Skip to content

Commit 68ca359

Browse files
committed
Fix context aware static completion
1 parent be035d1 commit 68ca359

3 files changed

Lines changed: 597 additions & 3 deletions

File tree

src/completion/builder.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,18 @@ impl Backend {
9494
/// (same-class access, e.g. `$this->`).
9595
/// - `Some(name)` where `name != target_class.name`: **public** and
9696
/// **protected** members are shown (the caller might be in a subclass).
97+
///
98+
/// `is_self_or_ancestor` should be `true` when the cursor is inside the
99+
/// target class itself or inside a class that (transitively) extends the
100+
/// target. When `true`, `__construct` is offered for `::` access
101+
/// (e.g. `self::__construct()`, `parent::__construct()`,
102+
/// `ClassName::__construct()` from within a subclass). When `false`,
103+
/// magic methods are suppressed entirely.
97104
pub(crate) fn build_completion_items(
98105
target_class: &ClassInfo,
99106
access_kind: AccessKind,
100107
current_class_name: Option<&str>,
108+
is_self_or_ancestor: bool,
101109
) -> Vec<CompletionItem> {
102110
// Determine whether we are inside the same class as the target.
103111
let same_class = current_class_name.is_some_and(|name| name == target_class.name);
@@ -108,12 +116,13 @@ impl Backend {
108116
// Methods — filtered by static / instance, excluding magic methods
109117
for method in &target_class.methods {
110118
// `__construct` is meaningful to call explicitly via `::` or
111-
// `parent::` (e.g. `parent::__construct(...)` in a child class),
112-
// so we only suppress it for `->` access. All other magic
113-
// methods are always suppressed.
119+
// `parent::` when inside the same class or a subclass
120+
// (e.g. `parent::__construct(...)`, `self::__construct()`).
121+
// Outside of that relationship, magic methods are suppressed.
114122
let is_constructor = method.name.eq_ignore_ascii_case("__construct");
115123
if Self::is_magic_method(&method.name) {
116124
let allow = is_constructor
125+
&& is_self_or_ancestor
117126
&& matches!(
118127
access_kind,
119128
AccessKind::DoubleColon | AccessKind::ParentDoubleColon
@@ -235,6 +244,19 @@ impl Backend {
235244
}
236245
}
237246

247+
// `::class` keyword — returns the fully qualified class name as a string.
248+
// Available on any class, interface, or enum via `::` access.
249+
if access_kind == AccessKind::DoubleColon || access_kind == AccessKind::ParentDoubleColon {
250+
items.push(CompletionItem {
251+
label: "class".to_string(),
252+
kind: Some(CompletionItemKind::KEYWORD),
253+
detail: Some("class-string".to_string()),
254+
insert_text: Some("class".to_string()),
255+
filter_text: Some("class".to_string()),
256+
..CompletionItem::default()
257+
});
258+
}
259+
238260
// Sort all items alphabetically (case-insensitive) and assign
239261
// sort_text so the editor preserves this ordering.
240262
items.sort_by(|a, b| {

src/server.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,43 @@ impl LanguageServer for Backend {
393393
for target_class in &candidates {
394394
let merged =
395395
Self::resolve_class_with_inheritance(target_class, &class_loader);
396+
397+
// Determine whether the cursor is inside the target
398+
// class itself or inside a (transitive) subclass.
399+
// This controls whether `__construct` is offered
400+
// via `::` access.
401+
let is_self_or_ancestor = if let Some(cc) = current_class {
402+
if cc.name == target_class.name {
403+
true
404+
} else {
405+
// Walk the parent chain of the current class
406+
// to see if the target is an ancestor.
407+
let mut ancestor_name = cc.parent_class.clone();
408+
let mut found = false;
409+
let mut depth = 0u32;
410+
while let Some(ref name) = ancestor_name {
411+
depth += 1;
412+
if depth > 20 {
413+
break;
414+
}
415+
if *name == target_class.name {
416+
found = true;
417+
break;
418+
}
419+
ancestor_name =
420+
class_loader(name).and_then(|ci| ci.parent_class.clone());
421+
}
422+
found
423+
}
424+
} else {
425+
false
426+
};
427+
396428
let items = Self::build_completion_items(
397429
&merged,
398430
effective_access,
399431
current_class_name,
432+
is_self_or_ancestor,
400433
);
401434
for item in items {
402435
if !all_items

0 commit comments

Comments
 (0)