@@ -9,9 +9,10 @@ use ruff_python_ast::name::Name;
99use ruff_python_codegen:: Stylist ;
1010use ruff_python_parser:: { Token , TokenAt , TokenKind , Tokens } ;
1111use ruff_text_size:: { Ranged , TextLen , TextRange , TextSize } ;
12+ use ty_python_semantic:: types:: UnionType ;
1213use ty_python_semantic:: {
13- Completion as SemanticCompletion , ModuleName , NameKind , SemanticModel ,
14- types:: { CycleDetector , Type } ,
14+ Completion as SemanticCompletion , KnownModule , ModuleName , NameKind , SemanticModel ,
15+ types:: { CycleDetector , KnownClass , Type } ,
1516} ;
1617
1718use crate :: docstring:: Docstring ;
@@ -82,6 +83,31 @@ impl<'db> Completions<'db> {
8283 fn force_add ( & mut self , completion : Completion < ' db > ) {
8384 self . items . push ( completion) ;
8485 }
86+
87+ /// Tags completions with whether they are known to be usable in
88+ /// a `raise` context.
89+ ///
90+ /// It's possible that some completions are usable in a `raise`
91+ /// but aren't marked by this method. That is, false negatives are
92+ /// possible but false positives are not.
93+ fn tag_raisable ( & mut self ) {
94+ let raisable_type = UnionType :: from_elements (
95+ self . db ,
96+ [
97+ KnownClass :: BaseException . to_subclass_of ( self . db ) ,
98+ KnownClass :: BaseException . to_instance ( self . db ) ,
99+ ] ,
100+ ) ;
101+ for item in & mut self . items {
102+ let Some ( ty) = item. ty else { continue } ;
103+ item. is_definitively_raisable = ty. is_assignable_to ( self . db , raisable_type) ;
104+ }
105+ }
106+
107+ /// Removes any completion that doesn't satisfy the given predicate.
108+ fn retain ( & mut self , predicate : impl FnMut ( & Completion < ' _ > ) -> bool ) {
109+ self . items . retain ( predicate) ;
110+ }
85111}
86112
87113impl < ' db > Extend < SemanticCompletion < ' db > > for Completions < ' db > {
@@ -153,6 +179,13 @@ pub struct Completion<'db> {
153179 /// Whether this item only exists for type checking purposes and
154180 /// will be missing at runtime
155181 pub is_type_check_only : bool ,
182+ /// Whether this item can definitively be used in a `raise` context.
183+ ///
184+ /// Note that this may not always be computed. (i.e., Only computed
185+ /// when we are in a `raise` context.) And also note that if this
186+ /// is `true`, then it's definitively usable in `raise`, but if
187+ /// it's `false`, it _may_ still be usable in `raise`.
188+ pub is_definitively_raisable : bool ,
156189 /// The documentation associated with this item, if
157190 /// available.
158191 pub documentation : Option < Docstring > ,
@@ -177,6 +210,7 @@ impl<'db> Completion<'db> {
177210 import : None ,
178211 builtin : semantic. builtin ,
179212 is_type_check_only,
213+ is_definitively_raisable : false ,
180214 documentation,
181215 }
182216 }
@@ -257,6 +291,7 @@ impl<'db> Completion<'db> {
257291 import : None ,
258292 builtin : false ,
259293 is_type_check_only : false ,
294+ is_definitively_raisable : false ,
260295 documentation : None ,
261296 }
262297 }
@@ -271,6 +306,7 @@ impl<'db> Completion<'db> {
271306 import : None ,
272307 builtin : true ,
273308 is_type_check_only : false ,
309+ is_definitively_raisable : false ,
274310 documentation : None ,
275311 }
276312 }
@@ -364,6 +400,27 @@ pub fn completion<'db>(
364400 }
365401 }
366402
403+ if is_raising_exception ( tokens) {
404+ completions. tag_raisable ( ) ;
405+
406+ // As a special case, and because it's a common footgun, we
407+ // specifically disallow `NotImplemented` in this context.
408+ // `NotImplementedError` should be used instead. So if we can
409+ // definitively detect `NotImplemented`, then we can safely
410+ // omit it from suggestions.
411+ let not_implemented_type = UnionType :: from_elements (
412+ db,
413+ [
414+ KnownClass :: NotImplementedType . to_subclass_of ( db) ,
415+ KnownClass :: NotImplementedType . to_instance ( db) ,
416+ ] ,
417+ ) ;
418+ completions. retain ( |item| {
419+ let Some ( ty) = item. ty else { return true } ;
420+ !ty. is_assignable_to ( db, not_implemented_type)
421+ } ) ;
422+ }
423+
367424 completions. into_completions ( )
368425}
369426
@@ -427,7 +484,8 @@ fn add_unimported_completions<'db>(
427484 let members = importer. members_in_scope_at ( scoped. node , scoped. node . start ( ) ) ;
428485
429486 for symbol in all_symbols ( db, & completions. query ) {
430- if symbol. module . file ( db) == Some ( file) {
487+ if symbol. module . file ( db) == Some ( file) || symbol. module . is_known ( db, KnownModule :: Builtins )
488+ {
431489 continue ;
432490 }
433491
@@ -450,6 +508,7 @@ fn add_unimported_completions<'db>(
450508 builtin : false ,
451509 // TODO: `is_type_check_only` requires inferring the type of the symbol
452510 is_type_check_only : false ,
511+ is_definitively_raisable : false ,
453512 documentation : None ,
454513 } ) ;
455514 }
@@ -1358,6 +1417,30 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
13581417 } )
13591418}
13601419
1420+ /// Returns true when the cursor is after a `raise` keyword.
1421+ fn is_raising_exception ( tokens : & [ Token ] ) -> bool {
1422+ /// The maximum number of tokens we're willing to
1423+ /// look-behind to find a `raise` keyword.
1424+ const LIMIT : usize = 10 ;
1425+
1426+ // This only looks for things like `raise foo.bar.baz.qu<CURSOR>`.
1427+ // Technically, any kind of expression is allowed after `raise`.
1428+ // But we may not always want to treat it specially. So we're
1429+ // rather conservative about what we consider "raising an
1430+ // exception" to be for the purposes of completions. The failure
1431+ // mode here is that we may wind up suggesting things that
1432+ // shouldn't be raised. The benefit is that when this heuristic
1433+ // does work, we won't suggest things that shouldn't be raised.
1434+ for token in tokens. iter ( ) . rev ( ) . take ( LIMIT ) {
1435+ match token. kind ( ) {
1436+ TokenKind :: Name | TokenKind :: Dot => continue ,
1437+ TokenKind :: Raise => return true ,
1438+ _ => return false ,
1439+ }
1440+ }
1441+ false
1442+ }
1443+
13611444/// Order completions according to the following rules:
13621445///
13631446/// 1) Names with no underscore prefix
@@ -1370,8 +1453,16 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
13701453/// This has the effect of putting all dunder attributes after "normal"
13711454/// attributes, and all single-underscore attributes after dunder attributes.
13721455fn compare_suggestions ( c1 : & Completion , c2 : & Completion ) -> Ordering {
1373- fn key < ' a > ( completion : & ' a Completion ) -> ( bool , bool , bool , NameKind , bool , & ' a Name ) {
1456+ fn key < ' a > ( completion : & ' a Completion ) -> ( bool , bool , bool , bool , NameKind , bool , & ' a Name ) {
13741457 (
1458+ // This is only true when we are both in a `raise` context
1459+ // *and* we know this suggestion is definitively usable
1460+ // in a `raise` context. So we should sort these before
1461+ // anything else.
1462+ !completion. is_definitively_raisable ,
1463+ // When `None`, a completion is for something in the
1464+ // current module, which we should generally prefer over
1465+ // something from outside the module.
13751466 completion. module_name . is_some ( ) ,
13761467 // At time of writing (2025-11-11), keyword completions
13771468 // are classified as builtins, which makes them sort after
0 commit comments