@@ -196,7 +196,7 @@ async fn test_completion_arrow_shows_only_non_static() {
196196}
197197
198198#[ tokio:: test]
199- async fn test_completion_double_colon_shows_only_static_and_constants ( ) {
199+ async fn test_completion_self_double_colon_shows_all_members ( ) {
200200 let backend = create_test_backend ( ) ;
201201
202202 let uri = Url :: parse ( "file:///dcolon.php" ) . unwrap ( ) ;
@@ -262,33 +262,35 @@ async fn test_completion_double_colon_shows_only_static_and_constants() {
262262 // Should include static method `create`
263263 assert ! (
264264 method_names. contains( & "create" ) ,
265- "DoubleColon should include static method 'create'"
265+ "self:: should include static method 'create'"
266266 ) ;
267- // Should NOT include non-static methods `run` and `helper`
267+ // self:: shows both static and non-static methods (PHP allows
268+ // calling instance methods via `self::` from within the class).
268269 assert ! (
269- ! method_names. contains( & "run" ) ,
270- "DoubleColon should exclude non-static method 'run'"
270+ method_names. contains( & "run" ) ,
271+ "self:: should include non-static method 'run'"
271272 ) ;
272273 assert ! (
273- ! method_names. contains( & "helper" ) ,
274- "DoubleColon should exclude non-static method 'helper'"
274+ method_names. contains( & "helper" ) ,
275+ "self:: should include non-static method 'helper'"
275276 ) ;
276277
277278 // Should include static property `$instance` (with $ prefix for :: access)
278279 assert ! (
279280 property_names. contains( & "$instance" ) ,
280- "DoubleColon should include static property '$instance'"
281+ "self:: should include static property '$instance'"
281282 ) ;
282- // Should NOT include non-static property `count` (or `$count`)
283+ // Non-static properties are still excluded — `self::$count`
284+ // is not valid PHP for instance properties.
283285 assert ! (
284286 !property_names. contains( & "count" ) && !property_names. contains( & "$count" ) ,
285- "DoubleColon should exclude non-static property 'count'"
287+ "self:: should exclude non-static property 'count'"
286288 ) ;
287289
288290 // Should include constant `MAX`
289291 assert ! (
290292 constant_names. contains( & "MAX" ) ,
291- "DoubleColon should include constant 'MAX'"
293+ "self:: should include constant 'MAX'"
292294 ) ;
293295 }
294296 _ => panic ! ( "Expected CompletionResponse::Array" ) ,
@@ -900,3 +902,174 @@ async fn test_double_colon_shows_class_keyword() {
900902 _ => panic ! ( "Expected CompletionResponse::Array" ) ,
901903 }
902904}
905+
906+ // ─── self:: and static:: show the same symbols as parent:: ──────────────────
907+
908+ /// `self::` and `static::` should show both static **and** non-static methods,
909+ /// just like `parent::` does, because PHP allows calling instance methods via
910+ /// `self::method()` and `static::method()` from within the class hierarchy.
911+ #[ tokio:: test]
912+ async fn test_self_and_static_show_non_static_methods_like_parent ( ) {
913+ let backend = create_test_backend ( ) ;
914+
915+ let uri = Url :: parse ( "file:///self_static_parity.php" ) . unwrap ( ) ;
916+ let text = concat ! (
917+ "<?php\n " ,
918+ "class Animal {\n " ,
919+ " public function breathe(): void {}\n " ,
920+ " public static function kingdom(): string { return 'Animalia'; }\n " ,
921+ " public const LEGS = 4;\n " ,
922+ " public static string $species = '';\n " ,
923+ " public int $age = 0;\n " ,
924+ "}\n " ,
925+ "class Dog extends Animal {\n " ,
926+ " public function bark(): void {}\n " ,
927+ " function testSelf() {\n " ,
928+ " self::\n " ,
929+ " }\n " ,
930+ " function testStatic() {\n " ,
931+ " static::\n " ,
932+ " }\n " ,
933+ "}\n " ,
934+ ) ;
935+
936+ let open_params = DidOpenTextDocumentParams {
937+ text_document : TextDocumentItem {
938+ uri : uri. clone ( ) ,
939+ language_id : "php" . to_string ( ) ,
940+ version : 1 ,
941+ text : text. to_string ( ) ,
942+ } ,
943+ } ;
944+ backend. did_open ( open_params) . await ;
945+
946+ // ── self:: (line 11, character 14) ──
947+ let self_params = CompletionParams {
948+ text_document_position : TextDocumentPositionParams {
949+ text_document : TextDocumentIdentifier { uri : uri. clone ( ) } ,
950+ position : Position {
951+ line : 11 ,
952+ character : 14 ,
953+ } ,
954+ } ,
955+ work_done_progress_params : WorkDoneProgressParams :: default ( ) ,
956+ partial_result_params : PartialResultParams :: default ( ) ,
957+ context : None ,
958+ } ;
959+
960+ let self_result = backend. completion ( self_params) . await . unwrap ( ) ;
961+ assert ! ( self_result. is_some( ) , "self:: should return completions" ) ;
962+
963+ let self_items = match self_result. unwrap ( ) {
964+ CompletionResponse :: Array ( items) => items,
965+ _ => panic ! ( "Expected CompletionResponse::Array" ) ,
966+ } ;
967+
968+ let self_method_names: Vec < & str > = self_items
969+ . iter ( )
970+ . filter ( |i| i. kind == Some ( CompletionItemKind :: METHOD ) )
971+ . map ( |i| i. filter_text . as_deref ( ) . unwrap ( ) )
972+ . collect ( ) ;
973+ let self_property_names: Vec < & str > = self_items
974+ . iter ( )
975+ . filter ( |i| i. kind == Some ( CompletionItemKind :: PROPERTY ) )
976+ . map ( |i| i. label . as_str ( ) )
977+ . collect ( ) ;
978+ let self_constant_names: Vec < & str > = self_items
979+ . iter ( )
980+ . filter ( |i| i. kind == Some ( CompletionItemKind :: CONSTANT ) )
981+ . map ( |i| i. label . as_str ( ) )
982+ . collect ( ) ;
983+
984+ // self:: should show both static and non-static methods
985+ assert ! (
986+ self_method_names. contains( & "breathe" ) ,
987+ "self:: should include inherited non-static 'breathe', got: {:?}" ,
988+ self_method_names
989+ ) ;
990+ assert ! (
991+ self_method_names. contains( & "kingdom" ) ,
992+ "self:: should include inherited static 'kingdom', got: {:?}" ,
993+ self_method_names
994+ ) ;
995+ assert ! (
996+ self_method_names. contains( & "bark" ) ,
997+ "self:: should include own non-static 'bark', got: {:?}" ,
998+ self_method_names
999+ ) ;
1000+ // Static properties shown, instance properties excluded
1001+ assert ! (
1002+ self_property_names. contains( & "$species" ) ,
1003+ "self:: should include static property '$species', got: {:?}" ,
1004+ self_property_names
1005+ ) ;
1006+ assert ! (
1007+ !self_property_names. contains( & "age" ) && !self_property_names. contains( & "$age" ) ,
1008+ "self:: should exclude non-static property 'age', got: {:?}" ,
1009+ self_property_names
1010+ ) ;
1011+ // Constants shown
1012+ assert ! (
1013+ self_constant_names. contains( & "LEGS" ) ,
1014+ "self:: should include constant 'LEGS', got: {:?}" ,
1015+ self_constant_names
1016+ ) ;
1017+
1018+ // ── static:: (line 14, character 16) ──
1019+ let static_params = CompletionParams {
1020+ text_document_position : TextDocumentPositionParams {
1021+ text_document : TextDocumentIdentifier { uri } ,
1022+ position : Position {
1023+ line : 14 ,
1024+ character : 16 ,
1025+ } ,
1026+ } ,
1027+ work_done_progress_params : WorkDoneProgressParams :: default ( ) ,
1028+ partial_result_params : PartialResultParams :: default ( ) ,
1029+ context : None ,
1030+ } ;
1031+
1032+ let static_result = backend. completion ( static_params) . await . unwrap ( ) ;
1033+ assert ! (
1034+ static_result. is_some( ) ,
1035+ "static:: should return completions"
1036+ ) ;
1037+
1038+ let static_items = match static_result. unwrap ( ) {
1039+ CompletionResponse :: Array ( items) => items,
1040+ _ => panic ! ( "Expected CompletionResponse::Array" ) ,
1041+ } ;
1042+
1043+ let static_method_names: Vec < & str > = static_items
1044+ . iter ( )
1045+ . filter ( |i| i. kind == Some ( CompletionItemKind :: METHOD ) )
1046+ . map ( |i| i. filter_text . as_deref ( ) . unwrap ( ) )
1047+ . collect ( ) ;
1048+ let static_constant_names: Vec < & str > = static_items
1049+ . iter ( )
1050+ . filter ( |i| i. kind == Some ( CompletionItemKind :: CONSTANT ) )
1051+ . map ( |i| i. label . as_str ( ) )
1052+ . collect ( ) ;
1053+
1054+ // static:: should also show both static and non-static methods
1055+ assert ! (
1056+ static_method_names. contains( & "breathe" ) ,
1057+ "static:: should include inherited non-static 'breathe', got: {:?}" ,
1058+ static_method_names
1059+ ) ;
1060+ assert ! (
1061+ static_method_names. contains( & "kingdom" ) ,
1062+ "static:: should include inherited static 'kingdom', got: {:?}" ,
1063+ static_method_names
1064+ ) ;
1065+ assert ! (
1066+ static_method_names. contains( & "bark" ) ,
1067+ "static:: should include own non-static 'bark', got: {:?}" ,
1068+ static_method_names
1069+ ) ;
1070+ assert ! (
1071+ static_constant_names. contains( & "LEGS" ) ,
1072+ "static:: should include constant 'LEGS', got: {:?}" ,
1073+ static_constant_names
1074+ ) ;
1075+ }
0 commit comments