Skip to content

Commit eba0193

Browse files
committed
Move the type engine out of the compleation module
1 parent 2e1402b commit eba0193

92 files changed

Lines changed: 619 additions & 625 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ and pipeline sections of [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
2020
for how the pieces fit together. A few durable landmarks, since the
2121
module tree moves around:
2222

23-
- **The shared type engine lives under `src/completion/`**, despite the
24-
name. `completion/resolver/` resolves a subject expression to a
25-
`ClassInfo`, and `completion/variable/forward_walk/` is the forward
23+
- **The shared type engine lives under `src/type_engine/`.**
24+
`type_engine/resolver/` resolves a subject expression to a
25+
`ClassInfo`, and `type_engine/variable/forward_walk/` is the forward
2626
walker that answers "what is the type of this expression here?" — it
2727
is shared by diagnostics, hover, go-to-definition, and signature help,
28-
not just completion. Do not build a second type-resolution path (see
29-
the anti-patterns below).
28+
not just completion. `completion/` holds completion-specific code only.
29+
Do not build a second type-resolution path (see the anti-patterns
30+
below).
3031
- **Parsing** is in `parser/` (PHP source → `ClassInfo`/`FunctionInfo`)
3132
and `docblock/` (PHPDoc tags, templates, conditional types).
3233
- **The data model** is in `types/` and `php_type/`.

docs/ARCHITECTURE.md

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ src/
5353
│ # Data model
5454
├── types/ # Core structures (ClassInfo, MethodInfo, ResolvedType, SubjectExpr, …)
5555
├── php_type/ # PHP type representation: parse, display, normalize, subtype, transform
56-
├── subject_expr.rs, subject_extraction.rs, subject_resolution.rs
57-
│ # Extracting and resolving the subject before ->, ?->, ::
5856
5957
│ # Parsing & indexing
6058
├── parser/ # mago-syntax AST → ClassInfo/FunctionInfo (classes, functions, use statements, attributes, incremental update_ast)
@@ -74,20 +72,25 @@ src/
7472
├── stubs.rs, stub_patches.rs # Embedded phpstorm-stubs index + hand patches
7573
├── phar.rs # Class discovery inside PHAR archives
7674
77-
│ # The shared type engine (lives under completion/, see note below)
78-
├── completion/
75+
│ # The shared type engine ("what is the type of this expression here?")
76+
├── type_engine/
77+
│ ├── subject_expr.rs, subject_extraction.rs, subject_resolution.rs
78+
│ │ # Extracting and resolving the subject before ->, ?->, ::
7979
│ ├── resolver/ # Subject expression → ClassInfo (type engine entry point) + chain cache, property narrowing
8080
│ ├── variable/ # Variable type resolution via assignment scanning
8181
│ │ ├── forward_walk/ # the forward walker (shared by diagnostics, hover, go-to-def, sig help)
8282
│ │ ├── rhs_resolution/ # right-hand-side expression resolution
8383
│ │ └── … # closure/foreach/class-string/raw-type resolution
8484
│ ├── call_resolution/ # Call expression + callable target resolution, return types, arg types
85-
│ ├── types/ # Type-hint → ClassInfo, narrowing/, conditional return types
86-
│ │ # ---- completion-specific below ----
85+
│ └── types/ # Type-hint → ClassInfo, narrowing/, conditional return types
86+
87+
│ # Completion (uses the type engine above)
88+
├── completion/
8789
│ ├── handler/ # Completion request orchestration (member access, class constants, named args, phpdoc)
8890
│ ├── context/ # Context-specific completion (catch, class, keyword, namespace, override, type-hint, …)
8991
│ ├── phpdoc/ # PHPDoc tag completion + docblock generation/
9092
│ ├── source/ # Source-text scanning (comment position, throws analysis)
93+
│ ├── variable/ # Variable-name completion + scope collection
9194
│ ├── builder.rs # Build LSP CompletionItems from resolved ClassInfo
9295
│ └── … # target.rs, array_shape.rs, named_args.rs, use_edit.rs, eloquent_string.rs, laravel_route_controller.rs, …
9396
@@ -127,16 +130,15 @@ tests/
127130
└── assert_type_runner.rs, fixture_runner.rs
128131
```
129132

130-
**The shared type engine lives under `completion/`, despite the name.**
131-
`completion/resolver/`, `completion/variable/` (including the
132-
`forward_walk/` forward walker), `completion/call_resolution/`, and
133-
`completion/types/` are the project's single type-resolution engine —
134-
they answer "what is the type of this expression here?" and are consumed
135-
by diagnostics, hover, go-to-definition, and signature help, not just
136-
completion (see [Forward Walker](#forward-walker) and
137-
[Name Resolution](#name-resolution) below). This naming is a known wart;
138-
extracting the engine into its own top-level module is tracked in
139-
[`docs/todo/refactor.md`](todo/refactor.md).
133+
**The shared type engine lives under `type_engine/`.** `type_engine/resolver/`,
134+
`type_engine/variable/` (including the `forward_walk/` forward walker),
135+
`type_engine/call_resolution/`, `type_engine/types/`, and the subject
136+
extraction/resolution files are the project's single type-resolution engine —
137+
they answer "what is the type of this expression here?" and are consumed by
138+
diagnostics, hover, go-to-definition, and signature help, not just completion
139+
(see [Forward Walker](#forward-walker) and [Name Resolution](#name-resolution)
140+
below). Do not build a second type-resolution path: extend the engine here so
141+
every consumer benefits.
140142

141143
## External Crates
142144

@@ -514,7 +516,7 @@ Current patches:
514516

515517
This is analogous to the [Laravel Class Patches](#laravel-class-patches) system but for built-in PHP functions rather than framework classes. When phpstorm-stubs gains proper annotations for a patched function, the corresponding patch can be deleted.
516518

517-
**When to add a patch here vs. hardcoded logic in `rhs_resolution.rs`:** if the correct behaviour can be expressed with `@template` / `@return` annotations (i.e. PHPStan's own stubs already have the fix), it belongs in `stub_patches.rs`. If the behaviour requires inspecting call-site argument *values* at resolution time (e.g. `array_map`'s callback return type, or `array_filter` preserving the input array's element type), it must stay as hardcoded logic in `rhs_resolution.rs` / `raw_type_inference.rs`. Those functions are tracked in `ARRAY_PRESERVING_FUNCS` and `ARRAY_ELEMENT_FUNCS` in `completion/variable/mod.rs`, with the full inventory in `docs/todo/completion.md` C1.
519+
**When to add a patch here vs. hardcoded logic in `rhs_resolution.rs`:** if the correct behaviour can be expressed with `@template` / `@return` annotations (i.e. PHPStan's own stubs already have the fix), it belongs in `stub_patches.rs`. If the behaviour requires inspecting call-site argument *values* at resolution time (e.g. `array_map`'s callback return type, or `array_filter` preserving the input array's element type), it must stay as hardcoded logic in `rhs_resolution.rs` / `raw_type_inference.rs`. Those functions are tracked in `ARRAY_PRESERVING_FUNCS` and `ARRAY_ELEMENT_FUNCS` in `type_engine/variable/mod.rs`, with the full inventory in `docs/todo/completion.md` C1.
518520

519521
#### Class patches
520522

@@ -636,9 +638,9 @@ Scope methods (both `scopeX` convention and `#[Scope]` attribute) are synthesize
636638

637639
Instead, scope injection happens in three places:
638640

639-
1. **Post-generic-substitution hook** (`completion/resolver.rs`, inside `type_hint_to_classes_depth`): after `resolve_class_fully` + `apply_generic_args` produces a `Builder<User>` class, the resolver detects that the result is an Eloquent Builder with a concrete model generic argument. It calls `build_scope_methods_for_builder(model_name, class_loader)` which loads the model, fully resolves it, extracts scope methods, and returns them as instance methods with `static` in return types substituted to the concrete model name. These are merged onto the Builder's method list, giving `$q->active()` after `$q = User::where(...)`.
641+
1. **Post-generic-substitution hook** (`type_engine/resolver/`, inside `type_hint_to_classes_depth`): after `resolve_class_fully` + `apply_generic_args` produces a `Builder<User>` class, the resolver detects that the result is an Eloquent Builder with a concrete model generic argument. It calls `build_scope_methods_for_builder(model_name, class_loader)` which loads the model, fully resolves it, extracts scope methods, and returns them as instance methods with `static` in return types substituted to the concrete model name. These are merged onto the Builder's method list, giving `$q->active()` after `$q = User::where(...)`.
640642

641-
2. **Scope body Builder enrichment** (`completion/variable_resolution.rs`, `enrich_builder_type_in_scope`): inside a scope method body, the `$query` parameter is typically typed as `Builder` without generic arguments. The enrichment function detects when the enclosing method is a scope (either the `scopeX` naming convention or the `#[Scope]` attribute) on a class that extends Eloquent Model, and the parameter type is `Builder` without generics. It rewrites the type to `Builder<EnclosingModel>`, which then flows through the generic-args path and triggers the post-substitution hook above. This makes `$query->otherScope()` resolve inside scope bodies.
643+
2. **Scope body Builder enrichment** (`type_engine/variable/resolution.rs`, `enrich_builder_type_in_scope`): inside a scope method body, the `$query` parameter is typically typed as `Builder` without generic arguments. The enrichment function detects when the enclosing method is a scope (either the `scopeX` naming convention or the `#[Scope]` attribute) on a class that extends Eloquent Model, and the parameter type is `Builder` without generics. It rewrites the type to `Builder<EnclosingModel>`, which then flows through the generic-args path and triggers the post-substitution hook above. This makes `$query->otherScope()` resolve inside scope bodies.
642644

643645
3. **Go-to-definition fallback** (`definition/member.rs`, `find_scope_on_builder_model`): when go-to-definition resolves a member on a Builder class and the normal lookup chain (own members, traits, parents, mixins, builder forwarding) fails, this fallback checks whether the member is a scope method injected from the model. It confirms the resolved candidate (with injected scopes) has the method, extracts the model name from the scope method's return type generic argument, loads the model, and finds the declaration through the model's inheritance chain. For `scopeX`-style scopes it looks for `scopeXxx`; for `#[Scope]`-attributed methods it falls back to the original method name. This bridges the gap between completion (which works on the merged ClassInfo) and GTD (which traces back to the declaring source file).
644646

@@ -1112,7 +1114,7 @@ Everything above diagnoses files the user has opened. `src/diagnostics/workspace
11121114

11131115
## Forward Walker
11141116

1115-
The forward walker (`src/completion/variable/forward_walk.rs`) walks
1117+
The forward walker (`src/type_engine/variable/forward_walk/`) walks
11161118
method bodies top-to-bottom, building a scope of variable types at
11171119
each statement boundary. It is shared by diagnostics, completion,
11181120
hover, go-to-definition, and signature help.

docs/todo/bugs.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ process(class: Product::class, flag: true, function ($p) {
3232

3333
The forward walker's closure-argument inference
3434
(`walk_closures_in_call_args` and `walk_closure_in_partial_call_args` in
35-
`src/completion/variable/forward_walk/diagnostic_walk.rs`, and the
35+
`src/type_engine/variable/forward_walk/diagnostic_walk.rs`, and the
3636
completion/hover-path counterpart `infer_callable_params_for_call` in
37-
`src/completion/variable/forward_walk/callable_inference.rs`) computes
37+
`src/type_engine/variable/forward_walk/callable_inference.rs`) computes
3838
`arg_idx` from `arguments.iter().enumerate()` — the argument's position
3939
in the *call*. That index is then used directly as the *declared*
4040
parameter index, both to decide whether the argument at that slot is a

docs/todo/diagnostics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ resolution. This eliminates the secondary resolvers entirely.
176176

177177
- `src/diagnostics/unknown_members.rs` — remove
178178
`resolve_scalar_subject_type` and `resolve_unresolvable_class_subject`
179-
- `src/completion/resolver.rs` — enrich the resolution result
179+
- `src/type_engine/resolver/` — enrich the resolution result
180180

181181
---
182182

docs/todo/eager-resolution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ removes those guards and the depth caps they protect:
263263
consumer queries them, so re-entry cannot happen.
264264

265265
2. **`RESOLVE_DEPTH` and `MAX_RESOLVE_TARGET_DEPTH` (60) in
266-
`completion/resolver.rs`.** Thread-local depth counter for
266+
`type_engine/resolver/`.** Thread-local depth counter for
267267
`resolve_target_classes_expr_inner`. Guards against mutual
268268
recursion between subject resolution, call-return resolution,
269269
and variable resolution. The limit of 60 (vs typical chain

docs/todo/inline-completion.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ completion resolver. No model, no data files, no latency.
121121

122122
The template engine reuses existing infrastructure:
123123

124-
- **Variable types:** `completion/variable/resolution.rs` already
124+
- **Variable types:** `type_engine/variable/resolution.rs` already
125125
resolves every variable in scope to a type. The template engine
126126
calls the same pipeline.
127127
- **Containing function:** The AST map already stores `MethodInfo`

docs/todo/refactor.md

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -214,66 +214,4 @@ Each item must include:
214214

215215
# Outstanding items
216216

217-
One task remains. It is a pure internal refactor and gets no changelog
218-
entry unless the work uncovers and fixes a concrete user-visible bug. Run
219-
the Rust CI checks (`cargo nextest run`, `cargo clippy -- -D warnings`,
220-
`cargo clippy --tests -- -D warnings`, `cargo fmt`) after the task.
221-
222-
## 5. Extract the shared type engine out of `completion/`
223-
224-
**What to do.** Despite the name, `src/completion/` houses the
225-
project's single type-resolution engine — the code that answers "what
226-
is the type of this expression here?" — consumed by diagnostics, hover,
227-
go-to-definition, and signature help, not just completion. The name
228-
misleads every new contributor and buries the most load-bearing
229-
subsystem inside a feature module. Move the engine into a top-level
230-
module (proposed `src/type_engine/`; name negotiable but **not**
231-
`resolve`/`resolution`, which collide with the existing `resolution.rs`
232-
class/function *name* lookup), leaving `completion/` with only
233-
completion-specific code. This is pure code motion — `impl Backend`
234-
blocks can live in any file of the crate, so methods move without
235-
becoming free functions.
236-
237-
**Engine subtrees to move** (whole directories): `completion/resolver/`,
238-
`completion/call_resolution/`, `completion/types/`, and the
239-
type-resolution half of `completion/variable/``resolution.rs`,
240-
`forward_walk/`, `rhs_resolution/`, `foreach_resolution.rs`,
241-
`closure_resolution.rs`, `class_string_resolution.rs`,
242-
`raw_type_inference.rs`. The root-level `subject_expr.rs`,
243-
`subject_extraction.rs`, and `subject_resolution.rs` are part of the
244-
engine too — fold them in.
245-
246-
**Stays in `completion/`:** `handler/`, `context/`, `phpdoc/`,
247-
`builder.rs`, `target.rs`, `array_shape.rs`, `array_callable.rs`,
248-
`named_args.rs`, `use_edit.rs`, `eloquent_string.rs`,
249-
`laravel_route_controller.rs`, `laravel_string_keys.rs`, and the one
250-
non-clean split: `completion/variable/completion.rs` is variable *name*
251-
completion (scope collection for the completion list), not type
252-
resolution, so it stays while its sibling type-resolution files move.
253-
254-
**Steps.**
255-
1. Create the new module and move the engine subtrees into it, updating
256-
`mod`/`use` declarations.
257-
2. Split `completion/variable/`: keep `completion.rs` (and any
258-
scope-collection helpers only it uses) under `completion/`; move the
259-
rest under `<engine>/variable/`.
260-
3. Update imports crate-wide (`crate::completion::resolver::…`
261-
`crate::type_engine::resolver::…`, etc.). Add thin `pub use`
262-
re-exports in `completion/mod.rs` only if they meaningfully shrink
263-
the diff; otherwise fix call sites directly.
264-
4. Update the module map and the "shared type engine lives under
265-
`completion/`" note in `docs/ARCHITECTURE.md`, and the Project
266-
Structure landmark in `AGENTS.md`, to point at the new module.
267-
268-
**Acceptance.** `cargo nextest run` passes with only mechanical
269-
import-path updates; behavior is unchanged; `completion/` contains only
270-
completion features; the type engine has one obvious top-level home.
271-
272-
**Why it matters.** Anti-pattern #6 in `AGENTS.md` ("do not build a
273-
second type-resolution path") depends on the engine being
274-
discoverable. While it hides under `completion/`, contributors reach
275-
for a "simpler" local resolver instead of finding the shared one. This
276-
is the largest churn of the set (crate-wide import moves), so it goes
277-
last.
278-
279-
---
217+
No outstanding items.

docs/todo/type-inference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ emits the concrete callable signature in the `@param` tag.
344344
## T20. Type narrowing reconciliation engine
345345
**Impact: Medium-High · Effort: High**
346346

347-
PHPantom's type narrowing in `completion/types/narrowing.rs` handles
347+
PHPantom's type narrowing in `type_engine/types/narrowing/` handles
348348
basic patterns (instanceof, is_* calls, null checks) but lacks the
349349
algebraic framework that PHPStan and Psalm use. Key gaps:
350350

src/analyse/run.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,9 @@ pub async fn run(options: AnalyseOptions) -> i32 {
283283
let _cache_guard =
284284
with_active_resolved_class_cache(&backend.resolved_class_cache);
285285
let _chain_guard =
286-
crate::completion::resolver::with_chain_resolution_cache();
286+
crate::type_engine::resolver::with_chain_resolution_cache();
287287
let _callable_guard =
288-
crate::completion::call_resolution::with_callable_target_cache();
288+
crate::type_engine::call_resolution::with_callable_target_cache();
289289
let _body_infer_guard = backend.activate_body_return_inferrer();
290290
let _auth_user_guard = backend.activate_auth_user_resolver();
291291

@@ -297,19 +297,19 @@ pub async fn run(options: AnalyseOptions) -> i32 {
297297
// collectors hit the cache (O(log N) lookup)
298298
// instead of doing a full backward scan.
299299
let _scope_guard =
300-
crate::completion::variable::forward_walk::with_diagnostic_scope_cache(
300+
crate::type_engine::variable::forward_walk::with_diagnostic_scope_cache(
301301
);
302302
let scope_t0 = Instant::now();
303303
{
304304
let file_ctx = backend.file_context(uri);
305305
let class_loader = backend.class_loader(&file_ctx);
306306
let function_loader_cl = backend.function_loader(&file_ctx);
307307
let constant_loader_cl = backend.constant_loader();
308-
let loaders = crate::completion::resolver::Loaders {
308+
let loaders = crate::type_engine::resolver::Loaders {
309309
function_loader: Some(&function_loader_cl),
310310
constant_loader: Some(&constant_loader_cl),
311311
};
312-
crate::completion::variable::forward_walk::build_diagnostic_scopes(
312+
crate::type_engine::variable::forward_walk::build_diagnostic_scopes(
313313
content,
314314
&file_ctx.classes,
315315
&class_loader,

src/code_actions/extract_function/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ pub(crate) use crate::code_actions::{
2727
CodeActionData, indent_of_line_at, indent_unit, make_code_action_data,
2828
};
2929
pub(crate) use crate::completion::phpdoc::generation::enrichment_plain;
30-
pub(crate) use crate::completion::resolver::Loaders;
3130
pub(crate) use crate::php_type::PhpType;
3231
pub(crate) use crate::scope_collector::ScopeMap;
3332
use crate::text_position::{offset_to_position, position_to_byte_offset};
33+
pub(crate) use crate::type_engine::resolver::Loaders;
3434
pub(crate) use crate::types::ClassInfo;
3535

3636
mod codegen;

0 commit comments

Comments
 (0)