1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
//! Centralized Laravel class patch system.
//!
//! After virtual members are applied during [`resolve_class_fully_inner`],
//! certain Laravel classes need post-resolution fixups that cannot be
//! expressed as virtual member providers (which add new members) but
//! instead modify existing members' type information.
//!
//! This module provides a single entry point, [`apply_laravel_patches`],
//! that dispatches to per-class patch functions based on the fully-qualified
//! class name. All Laravel-specific class mutations live here, making it
//! easy to audit and extend the patch inventory.
//!
//! ## Patch inventory
//!
//! 1. **`Eloquent\Builder::__call` / `__callStatic` return type.**
//! Overrides the `mixed` return type to `static` so that method chains
//! through unknown calls (scope dispatch, macro dispatch, Query\Builder
//! forwarding) preserve the Builder type.
//!
//! 2. **`Conditionable::when()` / `unless()` return type.**
//! The trait declares `@return $this|TWhenReturnType` (or a conditional
//! form in Larastan stubs). The unresolved `TWhenReturnType` template
//! parameter breaks `is_self_like_type` checks, degrading Builder chains.
//! The patch replaces the return type with `$this` so that chained
//! `when()` / `unless()` calls preserve the receiver type.
//!
//! 3. **Bare `Builder` return types on scope methods** are handled
//! separately in `scopes.rs` (`is_bare_builder_type`) because that
//! patch runs at scope-injection time (post-generic-substitution),
//! not during `resolve_class_fully_inner`. It is documented here
//! as part of the patch inventory but not dispatched from this module.
//!
//! 4. **`Redis\Connections\Connection` mixin.**
//! The base `Connection` class delegates all Redis commands to the
//! underlying `\Redis` client via `__call`, but lacks a `@mixin`
//! annotation. The patch injects `@mixin \Redis` **pre-resolution**
//! (in `resolve_class_fully_inner`, before virtual member providers
//! run) so that `collect_mixin_members` picks it up and merges
//! `del()`, `get()`, `set()`, etc. from the stubs. This patch is
//! not dispatched from `apply_laravel_patches` because that runs
//! post-resolution, after mixin collection has already completed.
//!
//! 5. **`DB` facade / `Connection` select method return types.**
//! The facade's `@method` annotations and the underlying
//! `Connection` class both declare `select()`,
//! `selectFromWriteConnection()`, and `selectResultSets()` as
//! returning bare `array`. The actual return type is
//! `array<int, stdClass>`. Similarly, `selectOne()` is declared as
//! `mixed` but actually returns `stdClass|null`. The patch
//! overrides these return types so that downstream property access
//! on query results resolves correctly.
use cratePhpType;
use crateClassInfo;
use ELOQUENT_BUILDER_FQN;
/// FQN of the `Conditionable` trait from `illuminate/support`.
const CONDITIONABLE_FQN: &str = "Illuminate\\Support\\Traits\\Conditionable";
/// FQN of the `DB` facade from `illuminate/support`.
const DB_FACADE_FQN: &str = "Illuminate\\Support\\Facades\\DB";
/// FQN of the base `Connection` class from `illuminate/database`.
const DB_CONNECTION_FQN: &str = "Illuminate\\Database\\Connection";
/// Apply all registered Laravel class patches to a fully-resolved class.
///
/// Called from [`resolve_class_fully_inner`] after virtual members have
/// been merged and before the result is cached. Dispatches to per-class
/// patch functions based on `fqn`.
///
/// This is also applied transitively: when a class uses the
/// `Conditionable` trait, the trait's `when()` / `unless()` methods are
/// merged into the class. The patch scans the merged method list by
/// name, so it fixes the return type regardless of whether the method
/// was inherited from the trait directly or through a parent class.
/// Override `__call` and `__callStatic` return types on Eloquent Builder
/// from `mixed` to `static`.
///
/// Builder's `__call` dispatches to scope methods (`callNamedScope`),
/// macros, and `Query\Builder` forwarding — all of which return `$this`.
/// The `@return mixed` docblock is a PHP limitation; the actual return
/// type is always the Builder instance. Patching this here means every
/// consumer of the resolved Builder (completion, diagnostics, hover)
/// automatically gets correct chain continuation through unknown methods.
/// Patch `when()` and `unless()` return types to `$this`.
///
/// The `Conditionable` trait declares these methods with return types
/// like `$this|TWhenReturnType` or the Larastan conditional form
/// `(TWhenReturnType is void|null ? $this : TWhenReturnType)`. In
/// either case the unresolved method-level template parameter
/// `TWhenReturnType` / `TUnlessReturnType` prevents `is_self_like_type`
/// from recognizing the return as self-referential, which breaks method
/// chain resolution on Builder and Collection.
///
/// Since we cannot currently bind method-level templates during chain
/// resolution, the pragmatic fix is to treat these methods as returning
/// `$this` unconditionally. This matches the common case (the callback
/// returns void and the method returns the receiver) and preserves
/// chain continuation.
/// Check whether a return type contains an unresolved template parameter
/// that would prevent `is_self_like_type` from matching.
///
/// Recognizes patterns like:
/// - `$this|TWhenReturnType` (union with an unknown non-self member)
/// - `TWhenReturnType` (bare template parameter)
/// - `static|TWhenReturnType` (union mixing self-like and template)
///
/// A type name is considered a template parameter if it starts with an
/// uppercase `T` followed by another uppercase letter, or if it is not
/// a known keyword / built-in type and is not fully-qualified (no `\`).
/// Heuristic: does this type look like an unresolved template parameter?
///
/// Template parameters in PHPDoc are typically `TFoo` (uppercase T + more).
/// We also catch any single bare name that is not a PHP keyword, not
/// fully-qualified, and not a self-reference.
/// Patch `select()`, `selectFromWriteConnection()`, `selectResultSets()`
/// return types from bare `array` to `array<int, stdClass>`, and
/// `selectOne()` from `mixed` to `stdClass|null`.
///
/// Both the `DB` facade (`@method` annotations) and the underlying
/// `Illuminate\Database\Connection` class declare these methods with
/// imprecise return types. The actual runtime return is always an
/// array of `stdClass` rows (or a single `stdClass|null` for
/// `selectOne`). Patching this here lets property access on query
/// results resolve correctly across the codebase.
/// Check whether a class uses the `Conditionable` trait (directly or
/// through its trait list / parent chain markers).
///
/// We check `used_traits` for both the FQN and the short name since
/// trait usage may be recorded in either form depending on how the
/// source was parsed.