Skip to content

Commit 60eb57c

Browse files
committed
Fix inherited @mixin
1 parent 5f9a868 commit 60eb57c

4 files changed

Lines changed: 317 additions & 40 deletions

File tree

example.php

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ enum Mode
9999
class Builder
100100
{
101101
/**
102-
* @return $this
102+
* @return static
103103
*/
104-
public function query(): self
104+
public static function query(): self
105105
{
106-
return $this;
106+
return new static();
107107
}
108108
}
109109

@@ -179,13 +179,13 @@ public function __toString(): string
179179
* @property-read bool $isAdmin
180180
* @method bool hasPermission(string $permission)
181181
*/
182-
class User extends Model implements Renderable
182+
class User extends Model implements Renderable // Press go-to on `Model` or `Renderable` to jump to there definitions
183183
{
184-
use HasTimestamps;
184+
use HasTimestamps; // Press go-to on `HasTimestamps` to jump to the trait definition
185185
use HasSlug;
186186

187187
public string $email;
188-
protected Status $status;
188+
protected Status $status; // Press go-to on `Status` to jump to the enum definition
189189
private array $roles = [];
190190

191191
public static string $defaultRole = 'user';
@@ -311,8 +311,8 @@ final class AdminUser extends User
311311

312312
public function __construct(string $name, string $email)
313313
{
314-
// parent:: completion shows parent's non-private members
315314
parent::__construct($name, $email);
315+
// parent:: shows inherited non-private methods and constants
316316
}
317317

318318
public function toArray(): array
@@ -462,6 +462,7 @@ function isRegularUser($value): bool
462462

463463
// Static member completion via ::
464464
User::$defaultRole; // Completion: static properties
465+
// Press go-to on `TYPE_ADMIN` to jump to its constant definition
465466
User::TYPE_ADMIN; // Completion: class constants
466467
User::findByEmail('a@b.c'); // Completion: static methods
467468
User::make('Bob'); // Completion: inherited static methods from Model
@@ -470,8 +471,6 @@ function isRegularUser($value): bool
470471
Status::Active; // Completion: enum cases
471472
Status::Active->label(); // Completion: methods on enum
472473

473-
// parent:: completion (in AdminUser context)
474-
// parent::toArray() — shows inherited non-private methods and constants
475474

476475
// self / static / $this resolution
477476
// Inside User class:
@@ -494,7 +493,7 @@ function isRegularUser($value): bool
494493
$made->getEmail(); // Resolves static return type
495494

496495
// Function return type resolution
497-
$u = createUser('Dana', 'dana@example.com');
496+
$u = createUser('Dana', 'dana@example.com'); // Press go-to on `createUser` to jump to the function definition
498497
$u->getName(); // Resolves createUser() return type -> User
499498

500499
// Constructor promoted properties (readonly)
@@ -653,42 +652,33 @@ function isRegularUser($value): bool
653652
break;
654653
}
655654

656-
// Go-to-definition targets:
657-
// - Hover over `User` to jump to its class definition
658-
// - Hover over `getEmail` to jump to its method definition
659-
// - Hover over `$email` property to jump to its definition
660-
// - Hover over `TYPE_ADMIN` to jump to its constant definition
661-
// - Hover over `Renderable` to jump to the interface definition
662-
// - Hover over `HasTimestamps` to jump to the trait definition
663-
// - Hover over `Status` to jump to the enum definition
664-
// - Hover over `Model` to jump to the parent class definition
665-
// - Hover over `createUser` to jump to the standalone function definition
666-
667-
// PHPDoc @property and @method (magic members)
668-
// $user->displayName — from @property tag on User
669-
// $user->hasPermission('edit') — from @method tag on User
670-
671655
// @mixin resolution
672-
// Model has @mixin HasTimestamps — mixin members appear in completion
656+
// Model has @mixin Builder — mixin members appear in completion
657+
$query = User::query(); // Press go-to on `query()` to jump to its class definition
673658

674659
// @var type override
675660
/** @var User $typed */
676661
$typed = getUnknownValue();
677-
$typed->getEmail(); // Type comes from @var docblock
662+
// Press go-to on `getEmail()` to jump to its method definition
663+
$email = $typed->getEmail(); // Type comes from @var docblock
664+
echo $email; // Press go-to on `$email` to jump to its assignment
678665

679666
// Inline @var docblock for variable type hints
680667
/** @var AdminUser $inlineTyped */
681668
$inlineTyped = getUnknownValue(AdminUser::class);
682669
$inlineTyped->grantPermission('write');
683670

684671
// Null-safe operator chaining
685-
$maybeUser = User::find(1);
672+
$maybeUser = User::find(1); // Press go-to on `User` to jump to its class definition
686673
$maybeUser?->getProfile()?->getDisplayName();
687674

675+
// PHPDoc @property and @method (magic members)
676+
echo $maybeUser->displayName; // from @property tag on User
677+
$maybeUser->hasPermission('edit'); // from @method tag on User
678+
688679
// Visibility filtering:
689-
// - Arrow completion shows only accessible members
690-
// - parent:: excludes private members
691-
// - Static :: shows only static members, constants, and enum cases
680+
// - $obj-> shows only accessible dynamic members
681+
// - MyClass:: shows only accessible static members
692682

693683
// Namespace and use statement resolution:
694684
// - Fully qualified: \Demo\User

src/completion/resolver.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,8 +2171,32 @@ impl Backend {
21712171
// its traits, or its parent chain. This models the PHP pattern
21722172
// where `@mixin` documents that magic methods (__call, __get,
21732173
// etc.) proxy to another class.
2174+
//
2175+
// Mixins are inherited: if `User extends Model` and `Model`
2176+
// has `@mixin Builder`, then `User` also gains Builder's
2177+
// members. We merge the class's own mixins first, then walk
2178+
// up the parent chain again to collect ancestor mixins.
21742179
Self::merge_mixins_into(&mut merged, &class.mixins, class_loader);
21752180

2181+
// Also merge mixins declared on ancestor classes.
2182+
let mut ancestor = class.clone();
2183+
let mut mixin_depth = 0u32;
2184+
while let Some(ref parent_name) = ancestor.parent_class {
2185+
mixin_depth += 1;
2186+
if mixin_depth > MAX_DEPTH {
2187+
break;
2188+
}
2189+
let parent = if let Some(p) = class_loader(parent_name) {
2190+
p
2191+
} else {
2192+
break;
2193+
};
2194+
if !parent.mixins.is_empty() {
2195+
Self::merge_mixins_into(&mut merged, &parent.mixins, class_loader);
2196+
}
2197+
ancestor = parent;
2198+
}
2199+
21762200
merged
21772201
}
21782202

src/definition/resolve.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,27 @@ impl Backend {
891891
return Some(found);
892892
}
893893

894+
// Also check @mixin classes declared on ancestor classes.
895+
// e.g. `User extends Model` where `Model` has `@mixin Builder`.
896+
let mut ancestor = class.clone();
897+
for _ in 0..MAX_DEPTH {
898+
let parent_name = match ancestor.parent_class.as_ref() {
899+
Some(name) => name.clone(),
900+
None => break,
901+
};
902+
let parent = match class_loader(&parent_name) {
903+
Some(p) => p,
904+
None => break,
905+
};
906+
if !parent.mixins.is_empty()
907+
&& let Some(found) =
908+
Self::find_declaring_in_mixins(&parent.mixins, member_name, class_loader, 0)
909+
{
910+
return Some(found);
911+
}
912+
ancestor = parent;
913+
}
914+
894915
None
895916
}
896917

0 commit comments

Comments
 (0)