@@ -15,79 +15,104 @@ within the same impact tier.
1515
1616---
1717
18- #### B1. Nullable type prefix not stripped during diagnostic class lookup
18+ #### B1. Nullable type not resolved to its base class
1919
2020| | |
2121| ---| ---|
2222| ** Impact** | Medium-High |
2323| ** Effort** | Low |
2424
25- When a variable's resolved type is ` ?ClassName ` (nullable shorthand),
26- the diagnostic pipeline uses the full string including the ` ? ` prefix
27- as the class lookup key. The lookup fails, producing a spurious
28- "subject type '?Foo' could not be resolved" warning even though ` Foo `
29- is a valid, loadable class.
25+ When a variable's type is ` ?ClassName ` , the type engine fails to
26+ resolve it to ` ClassName ` . The ` ? ` prefix is not stripped before class
27+ lookup, so the engine treats ` ?Foo ` as an unknown type even though
28+ ` Foo ` is a valid, loadable class. This breaks completion, hover, and
29+ go-to-definition for any variable whose type includes the nullable
30+ shorthand.
3031
31- ** Observed:** 3 diagnostics in ` shared ` for
32+ The completion pipeline's ` type_hint_to_classes ` already strips ` ? `
33+ (line 90 of ` completion/types/resolution.rs ` ), but other entry points
34+ into the type engine (e.g. ` resolve_variable_assignment_raw_type ` ,
35+ ` resolve_variable_type_string ` ) can return ` ?ClassName ` strings that
36+ are never cleaned before class lookup.
37+
38+ ** Observed:** 3 cases in ` shared ` for
3239` ?Luxplus\Core\Database\Model\Subscriptions\Subscription ` . The class
33- exists and loads fine without the ` ? ` prefix .
40+ exists and loads fine as ` Subscription ` .
3441
35- ** Fix:** Strip the leading ` ? ` (and/or ` null| ` / ` |null ` union
36- components) before class lookup in the diagnostic subject resolution
37- path. The completion pipeline already handles this correctly via
38- ` clean_type() ` , so the gap is specific to the diagnostic code path.
42+ ** Fix:** Ensure all paths that convert a raw type string to a
43+ ` ClassInfo ` strip the nullable prefix before class lookup. This may
44+ mean normalising the return value of the raw type resolvers, or
45+ ensuring every consumer calls ` strip_nullable ` / ` clean_type ` before
46+ passing type strings to ` class_loader ` .
3947
4048---
4149
42- #### B2. Generic type parameters not stripped during diagnostic class lookup
50+ #### B2. Generic type parameters prevent class resolution
4351
4452| | |
4553| ---| ---|
4654| ** Impact** | Medium |
4755| ** Effort** | Low |
4856
4957When a resolved type string includes generic parameters (e.g.
50- ` PaymentOptionLocaleCollection<PaymentOptionLocale> ` ), the diagnostic
51- pipeline uses the full parameterised string as the class lookup key.
52- The lookup fails because no class is registered under the name that
53- includes ` <...> ` .
58+ ` PaymentOptionLocaleCollection<PaymentOptionLocale> ` ), the type engine
59+ uses the full parameterised string as the class lookup key. The lookup
60+ fails because no class is registered under the name that includes
61+ ` <...> ` . This breaks completion and hover for any variable whose
62+ resolved type carries generic arguments.
5463
55- ** Observed:** 3 diagnostics in ` shared ` for
64+ ** Observed:** 3 cases in ` shared ` for
5665` PaymentOptionLocaleCollection<Luxplus\Core\Database\Model\Payments\PaymentOptionLocale> ` .
5766Methods like ` getTotalWeight() ` , ` isNotEmpty() ` , and ` first() ` all
58- exist on the class or its parent ` Collection ` .
67+ exist on the class or its parent ` Collection ` , but the type engine
68+ never reaches them.
5969
6070** Fix:** Strip everything from the first ` < ` onward before performing
61- class lookup. ` clean_type() ` already does this for the completion
62- pipeline; the diagnostic resolution path needs the same treatment.
71+ class lookup. The same raw-type-to-class conversion paths identified
72+ in B1 are affected. ` base_class_name() ` in ` type_strings.rs ` already
73+ combines ` clean_type ` + ` strip_generics ` and should be used
74+ consistently.
6375
6476---
6577
66- #### B3. Trait static/self suppression not applied inside closures
78+ #### B3. Type engine does not resolve ` $this ` / ` static ` inside traits
6779
6880| | |
6981| ---| ---|
7082| ** Impact** | Medium |
7183| ** Effort** | Low |
7284
73- The diagnostic pass suppresses ` $this ` /` self ` /` static ` /` parent ` member
74- access inside traits (since the host class provides those members). This
75- works for direct method bodies but fails when the access is inside a
76- closure nested within a trait method. The closure's byte offset is still
77- within the trait's range, and ` find_innermost_enclosing_class ` returns
78- the trait, but something in the suppression path doesn't fire.
79-
80- ** Observed:** ` SalesInfoGlobalTrait ` has ` static::where() ` and
81- ` static::query() ` inside ` retry(3, function() { ... }) ` . These produce
82- "Method 'where' not found on class 'SalesInfoGlobalTrait'" (2
83- diagnostics). Direct trait method bodies are correctly suppressed.
84-
85- ** Fix:** Investigate why the ` subject_text == "static" ` check on the
86- ` MemberAccess ` span doesn't match when the call is inside a closure.
87- The ` expr_to_subject_text ` function returns ` "static" ` for
88- ` Expression::Static ` , so the span should have the right subject text.
89- Possibly the closure introduces a scope boundary that changes how the
90- span is emitted or how the enclosing class is resolved.
85+ When a trait method (or a closure inside a trait method) accesses
86+ members via ` $this ` , ` self ` , ` static ` , or ` parent ` , the type engine
87+ resolves the subject to the trait itself rather than to the host class.
88+ Since the trait does not declare the members that the host class
89+ provides, the type engine reports the members as missing. This affects
90+ completion, hover, and go-to-definition for any trait that relies on
91+ host-class members.
92+
93+ The diagnostic pass has a suppression heuristic for this case, but
94+ the underlying problem is in the type engine: it should resolve
95+ ` $this ` /` static ` /` self ` inside a trait to the using class (when known)
96+ or defer resolution (when the host class is not known). The
97+ suppression heuristic also fails when the access is inside a closure
98+ nested within a trait method.
99+
100+ ** Observed:** 43 cases in ` shared ` . The largest cluster (41) is
101+ ` BusinessCentralErrorHandlerTrait ` where ` $this->model ` ,
102+ ` $this->eventType ` , etc. are properties provided by the host class.
103+ ` SalesInfoGlobalTrait ` contributes 2 cases where ` static::where() ` and
104+ ` static::query() ` are called inside a closure within a trait method.
105+
106+ ** Fix:** When the type engine encounters ` $this ` /` static ` /` self ` inside
107+ a trait, it should attempt to resolve to the known host class(es). For
108+ the analyze pass where no specific host class is open, the engine
109+ should recognise that trait member accesses are inherently incomplete
110+ and avoid reporting members as missing. The closure nesting issue is a
111+ separate symptom: ` find_innermost_enclosing_class ` does find the trait
112+ at the closure's offset, but the suppression check does not fire,
113+ suggesting the ` subject_text ` on the ` MemberAccess ` span differs from
114+ the expected ` "static" ` / ` "$this" ` keywords when emitted from inside
115+ a closure.
91116
92117---
93118
@@ -136,25 +161,26 @@ matches a known class), do not prepend the file namespace.
136161
137162---
138163
139- #### B6. Empty subject string in diagnostic messages
164+ #### B6. Empty subject string in type resolution
140165
141166| | |
142167| ---| ---|
143168| ** Impact** | Low |
144169| ** Effort** | Low |
145170
146- Some diagnostics display "Cannot resolve type of ''" with an empty
147- subject string. This happens when the subject extraction fails to
148- produce a text representation for complex expressions but a diagnostic
149- is still emitted.
171+ The subject extraction produces an empty string for complex expressions
172+ like ` ($a ?: $b)?->property ` , meaning the type engine has no subject
173+ to resolve. This manifests as "Cannot resolve type of ''" in
174+ diagnostics, but the underlying issue is that ` expr_to_subject_text `
175+ does not handle ternary-inside-nullable and similar compound patterns.
150176
151- ** Observed:** 5 diagnostics in ` shared ` with empty subject strings,
152- triggered by patterns like ` ($a ?: $b)?->property ` (ternary inside
153- nullable access) and similar compound expressions.
177+ ** Observed:** 5 cases in ` shared ` with empty subject strings.
154178
155- ** Fix:** Skip emitting the ` unresolved_member_access ` diagnostic when
156- the extracted subject text is empty, or improve ` expr_to_subject_text `
157- to handle ternary-in-nullable and similar compound patterns.
179+ ** Fix:** Extend ` expr_to_subject_text ` to handle parenthesised ternary
180+ expressions, short ternary (` ?: ` ), and null-coalesce (` ?? ` ) as subject
181+ bases. When the expression is too complex to represent as a subject
182+ string, the type engine should skip the access rather than attempt
183+ resolution with an empty key.
158184
159185---
160186
0 commit comments