@@ -76,17 +76,31 @@ impl Backend {
7676 format ! ( "{}({}){}" , method. name, params. join( ", " ) , ret)
7777 }
7878
79- /// Build completion items for a resolved class, filtered by access kind.
79+ /// Build completion items for a resolved class, filtered by access kind
80+ /// and visibility scope.
8081 ///
8182 /// - `Arrow` access: returns only non-static methods and properties.
8283 /// - `DoubleColon` access: returns only static methods, static properties, and constants.
8384 /// - `ParentDoubleColon` access: returns both static and non-static methods,
8485 /// static properties, and constants — but excludes private members.
8586 /// - `Other` access: returns all members.
87+ ///
88+ /// Visibility filtering based on `current_class_name`:
89+ /// - `None` (top-level code): only **public** members are shown.
90+ /// - `Some(name)` where `name == target_class.name`: all members are shown
91+ /// (same-class access, e.g. `$this->`).
92+ /// - `Some(name)` where `name != target_class.name`: **public** and
93+ /// **protected** members are shown (the caller might be in a subclass).
8694 pub ( crate ) fn build_completion_items (
8795 target_class : & ClassInfo ,
8896 access_kind : AccessKind ,
97+ current_class_name : Option < & str > ,
8998 ) -> Vec < CompletionItem > {
99+ // Determine whether we are inside the same class as the target.
100+ let same_class = current_class_name
101+ . is_some_and ( |name| name == target_class. name ) ;
102+ // Inside *some* class (possibly a subclass) — show protected.
103+ let in_class = current_class_name. is_some ( ) ;
90104 let mut items: Vec < CompletionItem > = Vec :: new ( ) ;
91105
92106 // Methods — filtered by static / instance, excluding magic methods
@@ -107,10 +121,14 @@ impl Backend {
107121 }
108122 }
109123
110- // parent:: excludes private members
111- if access_kind == AccessKind :: ParentDoubleColon
112- && method. visibility == Visibility :: Private
113- {
124+ // Visibility filtering:
125+ // - private: only visible from within the same class
126+ // - protected: visible from the same class or a subclass
127+ // (we approximate by allowing when inside any class)
128+ if method. visibility == Visibility :: Private && !same_class {
129+ continue ;
130+ }
131+ if method. visibility == Visibility :: Protected && !same_class && !in_class {
114132 continue ;
115133 }
116134
@@ -142,10 +160,10 @@ impl Backend {
142160
143161 // Properties — filtered by static / instance
144162 for property in & target_class. properties {
145- // parent:: excludes private members
146- if access_kind == AccessKind :: ParentDoubleColon
147- && property . visibility == Visibility :: Private
148- {
163+ if property . visibility == Visibility :: Private && !same_class {
164+ continue ;
165+ }
166+ if property . visibility == Visibility :: Protected && !same_class && !in_class {
149167 continue ;
150168 }
151169
@@ -191,10 +209,10 @@ impl Backend {
191209 || access_kind == AccessKind :: Other
192210 {
193211 for constant in & target_class. constants {
194- // parent:: excludes private members
195- if access_kind == AccessKind :: ParentDoubleColon
196- && constant . visibility == Visibility :: Private
197- {
212+ if constant . visibility == Visibility :: Private && !same_class {
213+ continue ;
214+ }
215+ if constant . visibility == Visibility :: Protected && !same_class && !in_class {
198216 continue ;
199217 }
200218
0 commit comments