@@ -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,20 @@ 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+ completions. retain ( |item| {
412+ let Some ( ty) = item. ty else { return true } ;
413+ !ty. is_notimplemented ( db)
414+ } ) ;
415+ }
416+
367417 completions. into_completions ( )
368418}
369419
@@ -427,7 +477,8 @@ fn add_unimported_completions<'db>(
427477 let members = importer. members_in_scope_at ( scoped. node , scoped. node . start ( ) ) ;
428478
429479 for symbol in all_symbols ( db, & completions. query ) {
430- if symbol. module . file ( db) == Some ( file) {
480+ if symbol. module . file ( db) == Some ( file) || symbol. module . is_known ( db, KnownModule :: Builtins )
481+ {
431482 continue ;
432483 }
433484
@@ -450,6 +501,7 @@ fn add_unimported_completions<'db>(
450501 builtin : false ,
451502 // TODO: `is_type_check_only` requires inferring the type of the symbol
452503 is_type_check_only : false ,
504+ is_definitively_raisable : false ,
453505 documentation : None ,
454506 } ) ;
455507 }
@@ -1358,6 +1410,30 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
13581410 } )
13591411}
13601412
1413+ /// Returns true when the cursor is after a `raise` keyword.
1414+ fn is_raising_exception ( tokens : & [ Token ] ) -> bool {
1415+ /// The maximum number of tokens we're willing to
1416+ /// look-behind to find a `raise` keyword.
1417+ const LIMIT : usize = 10 ;
1418+
1419+ // This only looks for things like `raise foo.bar.baz.qu<CURSOR>`.
1420+ // Technically, any kind of expression is allowed after `raise`.
1421+ // But we may not always want to treat it specially. So we're
1422+ // rather conservative about what we consider "raising an
1423+ // exception" to be for the purposes of completions. The failure
1424+ // mode here is that we may wind up suggesting things that
1425+ // shouldn't be raised. The benefit is that when this heuristic
1426+ // does work, we won't suggest things that shouldn't be raised.
1427+ for token in tokens. iter ( ) . rev ( ) . take ( LIMIT ) {
1428+ match token. kind ( ) {
1429+ TokenKind :: Name | TokenKind :: Dot => continue ,
1430+ TokenKind :: Raise => return true ,
1431+ _ => return false ,
1432+ }
1433+ }
1434+ false
1435+ }
1436+
13611437/// Order completions according to the following rules:
13621438///
13631439/// 1) Names with no underscore prefix
@@ -1370,8 +1446,16 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
13701446/// This has the effect of putting all dunder attributes after "normal"
13711447/// attributes, and all single-underscore attributes after dunder attributes.
13721448fn compare_suggestions ( c1 : & Completion , c2 : & Completion ) -> Ordering {
1373- fn key < ' a > ( completion : & ' a Completion ) -> ( bool , bool , bool , NameKind , bool , & ' a Name ) {
1449+ fn key < ' a > ( completion : & ' a Completion ) -> ( bool , bool , bool , bool , NameKind , bool , & ' a Name ) {
13741450 (
1451+ // This is only true when we are both in a `raise` context
1452+ // *and* we know this suggestion is definitively usable
1453+ // in a `raise` context. So we should sort these before
1454+ // anything else.
1455+ !completion. is_definitively_raisable ,
1456+ // When `None`, a completion is for something in the
1457+ // current module, which we should generally prefer over
1458+ // something from outside the module.
13751459 completion. module_name . is_some ( ) ,
13761460 // At time of writing (2025-11-11), keyword completions
13771461 // are classified as builtins, which makes them sort after
0 commit comments