@@ -5,7 +5,7 @@ use ruff_db::parsed::{ParsedModuleRef, parsed_module};
5
5
use ruff_python_ast as ast;
6
6
use ruff_python_parser:: { Token , TokenAt , TokenKind } ;
7
7
use ruff_text_size:: { Ranged , TextRange , TextSize } ;
8
- use ty_python_semantic:: { Completion , SemanticModel } ;
8
+ use ty_python_semantic:: { Completion , NameKind , SemanticModel } ;
9
9
10
10
use crate :: Db ;
11
11
use crate :: find_node:: covering_node;
@@ -325,38 +325,7 @@ fn import_from_tokens(tokens: &[Token]) -> Option<&Token> {
325
325
/// This has the effect of putting all dunder attributes after "normal"
326
326
/// attributes, and all single-underscore attributes after dunder attributes.
327
327
fn compare_suggestions ( c1 : & Completion , c2 : & Completion ) -> Ordering {
328
- /// A helper type for sorting completions based only on name.
329
- ///
330
- /// This sorts "normal" names first, then dunder names and finally
331
- /// single-underscore names. This matches the order of the variants defined for
332
- /// this enum, which is in turn picked up by the derived trait implementation
333
- /// for `Ord`.
334
- #[ derive( Clone , Copy , Eq , PartialEq , PartialOrd , Ord ) ]
335
- enum Kind {
336
- Normal ,
337
- Dunder ,
338
- Sunder ,
339
- }
340
-
341
- impl Kind {
342
- fn classify ( c : & Completion ) -> Kind {
343
- // Dunder needs a prefix and suffix double underscore.
344
- // When there's only a prefix double underscore, this
345
- // results in explicit name mangling. We let that be
346
- // classified as-if they were single underscore names.
347
- //
348
- // Ref: <https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers>
349
- if c. name . starts_with ( "__" ) && c. name . ends_with ( "__" ) {
350
- Kind :: Dunder
351
- } else if c. name . starts_with ( '_' ) {
352
- Kind :: Sunder
353
- } else {
354
- Kind :: Normal
355
- }
356
- }
357
- }
358
-
359
- let ( kind1, kind2) = ( Kind :: classify ( c1) , Kind :: classify ( c2) ) ;
328
+ let ( kind1, kind2) = ( NameKind :: classify ( & c1. name ) , NameKind :: classify ( & c2. name ) ) ;
360
329
kind1. cmp ( & kind2) . then_with ( || c1. name . cmp ( & c2. name ) )
361
330
}
362
331
@@ -472,6 +441,11 @@ mod tests {
472
441
" ,
473
442
) ;
474
443
test. assert_completions_include ( "filter" ) ;
444
+ // Sunder items should be filtered out
445
+ test. assert_completions_do_not_include ( "_T" ) ;
446
+ // Dunder attributes should not be stripped
447
+ test. assert_completions_include ( "__annotations__" ) ;
448
+ // See `private_symbols_in_stub` for more comprehensive testing private of symbol filtering.
475
449
}
476
450
477
451
#[ test]
@@ -536,6 +510,112 @@ re.<CURSOR>
536
510
test. assert_completions_include ( "findall" ) ;
537
511
}
538
512
513
+ #[ test]
514
+ fn private_symbols_in_stub ( ) {
515
+ let test = CursorTest :: builder ( )
516
+ . source (
517
+ "package/__init__.pyi" ,
518
+ r#"\
519
+ from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol
520
+
521
+ public_name = 1
522
+ _private_name = 1
523
+ __mangled_name = 1
524
+ __dunder_name__ = 1
525
+
526
+ public_type_var = TypeVar("public_type_var")
527
+ _private_type_var = TypeVar("_private_type_var")
528
+ __mangled_type_var = TypeVar("__mangled_type_var")
529
+
530
+ public_param_spec = ParamSpec("public_param_spec")
531
+ _private_param_spec = ParamSpec("_private_param_spec")
532
+
533
+ public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
534
+ _private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")
535
+
536
+ public_explicit_type_alias: TypeAlias = Literal[1]
537
+ _private_explicit_type_alias: TypeAlias = Literal[1]
538
+
539
+ class PublicProtocol(Protocol):
540
+ def method(self) -> None: ...
541
+
542
+ class _PrivateProtocol(Protocol):
543
+ def method(self) -> None: ...
544
+ "# ,
545
+ )
546
+ . source ( "main.py" , "import package; package.<CURSOR>" )
547
+ . build ( ) ;
548
+ test. assert_completions_include ( "public_name" ) ;
549
+ test. assert_completions_include ( "_private_name" ) ;
550
+ test. assert_completions_include ( "__mangled_name" ) ;
551
+ test. assert_completions_include ( "__dunder_name__" ) ;
552
+ test. assert_completions_include ( "public_type_var" ) ;
553
+ test. assert_completions_do_not_include ( "_private_type_var" ) ;
554
+ test. assert_completions_do_not_include ( "__mangled_type_var" ) ;
555
+ test. assert_completions_include ( "public_param_spec" ) ;
556
+ test. assert_completions_do_not_include ( "_private_param_spec" ) ;
557
+ test. assert_completions_include ( "public_type_var_tuple" ) ;
558
+ test. assert_completions_do_not_include ( "_private_type_var_tuple" ) ;
559
+ test. assert_completions_include ( "public_explicit_type_alias" ) ;
560
+ test. assert_completions_include ( "_private_explicit_type_alias" ) ;
561
+ test. assert_completions_include ( "PublicProtocol" ) ;
562
+ test. assert_completions_do_not_include ( "_PrivateProtocol" ) ;
563
+ }
564
+
565
+ /// Unlike [`private_symbols_in_stub`], this test doesn't use a `.pyi` file so all of the names
566
+ /// are visible.
567
+ #[ test]
568
+ fn private_symbols_in_module ( ) {
569
+ let test = CursorTest :: builder ( )
570
+ . source (
571
+ "package/__init__.py" ,
572
+ r#"\
573
+ from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol
574
+
575
+ public_name = 1
576
+ _private_name = 1
577
+ __mangled_name = 1
578
+ __dunder_name__ = 1
579
+
580
+ public_type_var = TypeVar("public_type_var")
581
+ _private_type_var = TypeVar("_private_type_var")
582
+ __mangled_type_var = TypeVar("__mangled_type_var")
583
+
584
+ public_param_spec = ParamSpec("public_param_spec")
585
+ _private_param_spec = ParamSpec("_private_param_spec")
586
+
587
+ public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
588
+ _private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")
589
+
590
+ public_explicit_type_alias: TypeAlias = Literal[1]
591
+ _private_explicit_type_alias: TypeAlias = Literal[1]
592
+
593
+ class PublicProtocol(Protocol):
594
+ def method(self) -> None: ...
595
+
596
+ class _PrivateProtocol(Protocol):
597
+ def method(self) -> None: ...
598
+ "# ,
599
+ )
600
+ . source ( "main.py" , "import package; package.<CURSOR>" )
601
+ . build ( ) ;
602
+ test. assert_completions_include ( "public_name" ) ;
603
+ test. assert_completions_include ( "_private_name" ) ;
604
+ test. assert_completions_include ( "__mangled_name" ) ;
605
+ test. assert_completions_include ( "__dunder_name__" ) ;
606
+ test. assert_completions_include ( "public_type_var" ) ;
607
+ test. assert_completions_include ( "_private_type_var" ) ;
608
+ test. assert_completions_include ( "__mangled_type_var" ) ;
609
+ test. assert_completions_include ( "public_param_spec" ) ;
610
+ test. assert_completions_include ( "_private_param_spec" ) ;
611
+ test. assert_completions_include ( "public_type_var_tuple" ) ;
612
+ test. assert_completions_include ( "_private_type_var_tuple" ) ;
613
+ test. assert_completions_include ( "public_explicit_type_alias" ) ;
614
+ test. assert_completions_include ( "_private_explicit_type_alias" ) ;
615
+ test. assert_completions_include ( "PublicProtocol" ) ;
616
+ test. assert_completions_include ( "_PrivateProtocol" ) ;
617
+ }
618
+
539
619
#[ test]
540
620
fn one_function_prefix ( ) {
541
621
let test = cursor_test (
0 commit comments