1
1
use crate :: find_node:: covering_node;
2
2
use crate :: { Db , HasNavigationTargets , NavigationTargets , RangedValue } ;
3
+ use red_knot_python_semantic:: types:: Type ;
3
4
use red_knot_python_semantic:: { HasType , SemanticModel } ;
4
5
use ruff_db:: files:: { File , FileRange } ;
5
6
use ruff_db:: parsed:: { parsed_module, ParsedModule } ;
@@ -16,31 +17,7 @@ pub fn goto_type_definition(
16
17
let goto_target = find_goto_target ( parsed, offset) ?;
17
18
18
19
let model = SemanticModel :: new ( db. upcast ( ) , file) ;
19
-
20
- let ty = match goto_target {
21
- GotoTarget :: Expression ( expression) => expression. inferred_type ( & model) ,
22
- GotoTarget :: FunctionDef ( function) => function. inferred_type ( & model) ,
23
- GotoTarget :: ClassDef ( class) => class. inferred_type ( & model) ,
24
- GotoTarget :: Parameter ( parameter) => parameter. inferred_type ( & model) ,
25
- GotoTarget :: Alias ( alias) => alias. inferred_type ( & model) ,
26
- GotoTarget :: ExceptVariable ( except) => except. inferred_type ( & model) ,
27
- GotoTarget :: KeywordArgument ( argument) => {
28
- // TODO: Pyright resolves the declared type of the matching parameter. This seems more accurate
29
- // than using the inferred value.
30
- argument. value . inferred_type ( & model)
31
- }
32
- // TODO: Support identifier targets
33
- GotoTarget :: PatternMatchRest ( _)
34
- | GotoTarget :: PatternKeywordArgument ( _)
35
- | GotoTarget :: PatternMatchStarName ( _)
36
- | GotoTarget :: PatternMatchAsName ( _)
37
- | GotoTarget :: ImportedModule ( _)
38
- | GotoTarget :: TypeParamTypeVarName ( _)
39
- | GotoTarget :: TypeParamParamSpecName ( _)
40
- | GotoTarget :: TypeParamTypeVarTupleName ( _)
41
- | GotoTarget :: NonLocal { .. }
42
- | GotoTarget :: Globals { .. } => return None ,
43
- } ;
20
+ let ty = goto_target. inferred_type ( & model) ?;
44
21
45
22
tracing:: debug!(
46
23
"Inferred type of covering node is {}" ,
@@ -149,6 +126,37 @@ pub(crate) enum GotoTarget<'a> {
149
126
} ,
150
127
}
151
128
129
+ impl < ' db > GotoTarget < ' db > {
130
+ pub ( crate ) fn inferred_type ( self , model : & SemanticModel < ' db > ) -> Option < Type < ' db > > {
131
+ let ty = match self {
132
+ GotoTarget :: Expression ( expression) => expression. inferred_type ( model) ,
133
+ GotoTarget :: FunctionDef ( function) => function. inferred_type ( model) ,
134
+ GotoTarget :: ClassDef ( class) => class. inferred_type ( model) ,
135
+ GotoTarget :: Parameter ( parameter) => parameter. inferred_type ( model) ,
136
+ GotoTarget :: Alias ( alias) => alias. inferred_type ( model) ,
137
+ GotoTarget :: ExceptVariable ( except) => except. inferred_type ( model) ,
138
+ GotoTarget :: KeywordArgument ( argument) => {
139
+ // TODO: Pyright resolves the declared type of the matching parameter. This seems more accurate
140
+ // than using the inferred value.
141
+ argument. value . inferred_type ( model)
142
+ }
143
+ // TODO: Support identifier targets
144
+ GotoTarget :: PatternMatchRest ( _)
145
+ | GotoTarget :: PatternKeywordArgument ( _)
146
+ | GotoTarget :: PatternMatchStarName ( _)
147
+ | GotoTarget :: PatternMatchAsName ( _)
148
+ | GotoTarget :: ImportedModule ( _)
149
+ | GotoTarget :: TypeParamTypeVarName ( _)
150
+ | GotoTarget :: TypeParamParamSpecName ( _)
151
+ | GotoTarget :: TypeParamTypeVarTupleName ( _)
152
+ | GotoTarget :: NonLocal { .. }
153
+ | GotoTarget :: Globals { .. } => return None ,
154
+ } ;
155
+
156
+ Some ( ty)
157
+ }
158
+ }
159
+
152
160
impl Ranged for GotoTarget < ' _ > {
153
161
fn range ( & self ) -> TextRange {
154
162
match self {
@@ -174,16 +182,18 @@ impl Ranged for GotoTarget<'_> {
174
182
}
175
183
176
184
pub ( crate ) fn find_goto_target ( parsed : & ParsedModule , offset : TextSize ) -> Option < GotoTarget > {
177
- let token = parsed. tokens ( ) . at_offset ( offset) . find ( |token| {
178
- matches ! (
179
- token. kind( ) ,
185
+ let token = parsed
186
+ . tokens ( )
187
+ . at_offset ( offset)
188
+ . max_by_key ( |token| match token. kind ( ) {
180
189
TokenKind :: Name
181
- | TokenKind :: String
182
- | TokenKind :: Complex
183
- | TokenKind :: Float
184
- | TokenKind :: Int
185
- )
186
- } ) ?;
190
+ | TokenKind :: String
191
+ | TokenKind :: Complex
192
+ | TokenKind :: Float
193
+ | TokenKind :: Int => 1 ,
194
+ _ => 0 ,
195
+ } ) ?;
196
+
187
197
let covering_node = covering_node ( parsed. syntax ( ) . into ( ) , token. range ( ) )
188
198
. find ( |node| node. is_identifier ( ) || node. is_expression ( ) )
189
199
. ok ( ) ?;
@@ -241,27 +251,18 @@ pub(crate) fn find_goto_target(parsed: &ParsedModule, offset: TextSize) -> Optio
241
251
242
252
#[ cfg( test) ]
243
253
mod tests {
244
- use std:: fmt:: Write ;
245
-
246
- use crate :: db:: tests:: TestDb ;
254
+ use crate :: tests:: { cursor_test, CursorTest , IntoDiagnostic } ;
247
255
use crate :: { goto_type_definition, NavigationTarget } ;
248
256
use insta:: assert_snapshot;
249
- use insta:: internals:: SettingsBindDropGuard ;
250
- use red_knot_python_semantic:: {
251
- Program , ProgramSettings , PythonPath , PythonPlatform , SearchPathSettings ,
252
- } ;
253
257
use ruff_db:: diagnostic:: {
254
- Annotation , Diagnostic , DiagnosticFormat , DiagnosticId , DisplayDiagnosticConfig , LintName ,
255
- Severity , Span , SubDiagnostic ,
258
+ Annotation , Diagnostic , DiagnosticId , LintName , Severity , Span , SubDiagnostic ,
256
259
} ;
257
- use ruff_db:: files:: { system_path_to_file, File , FileRange } ;
258
- use ruff_db:: system:: { DbWithWritableSystem , SystemPath , SystemPathBuf } ;
259
- use ruff_python_ast:: PythonVersion ;
260
- use ruff_text_size:: { Ranged , TextSize } ;
260
+ use ruff_db:: files:: FileRange ;
261
+ use ruff_text_size:: Ranged ;
261
262
262
263
#[ test]
263
264
fn goto_type_of_expression_with_class_type ( ) {
264
- let test = goto_test (
265
+ let test = cursor_test (
265
266
r#"
266
267
class Test: ...
267
268
@@ -291,7 +292,7 @@ mod tests {
291
292
292
293
#[ test]
293
294
fn goto_type_of_expression_with_function_type ( ) {
294
- let test = goto_test (
295
+ let test = cursor_test (
295
296
r#"
296
297
def foo(a, b): ...
297
298
@@ -323,7 +324,7 @@ mod tests {
323
324
324
325
#[ test]
325
326
fn goto_type_of_expression_with_union_type ( ) {
326
- let test = goto_test (
327
+ let test = cursor_test (
327
328
r#"
328
329
329
330
def foo(a, b): ...
@@ -380,7 +381,7 @@ mod tests {
380
381
381
382
#[ test]
382
383
fn goto_type_of_expression_with_module ( ) {
383
- let mut test = goto_test (
384
+ let mut test = cursor_test (
384
385
r#"
385
386
import lib
386
387
@@ -410,7 +411,7 @@ mod tests {
410
411
411
412
#[ test]
412
413
fn goto_type_of_expression_with_literal_type ( ) {
413
- let test = goto_test (
414
+ let test = cursor_test (
414
415
r#"
415
416
a: str = "test"
416
417
@@ -441,7 +442,7 @@ mod tests {
441
442
}
442
443
#[ test]
443
444
fn goto_type_of_expression_with_literal_node ( ) {
444
- let test = goto_test (
445
+ let test = cursor_test (
445
446
r#"
446
447
a: str = "te<CURSOR>st"
447
448
"# ,
@@ -469,7 +470,7 @@ mod tests {
469
470
470
471
#[ test]
471
472
fn goto_type_of_expression_with_type_var_type ( ) {
472
- let test = goto_test (
473
+ let test = cursor_test (
473
474
r#"
474
475
type Alias[T: int = bool] = list[T<CURSOR>]
475
476
"# ,
@@ -493,7 +494,7 @@ mod tests {
493
494
494
495
#[ test]
495
496
fn goto_type_of_expression_with_type_param_spec ( ) {
496
- let test = goto_test (
497
+ let test = cursor_test (
497
498
r#"
498
499
type Alias[**P = [int, str]] = Callable[P<CURSOR>, int]
499
500
"# ,
@@ -507,7 +508,7 @@ mod tests {
507
508
508
509
#[ test]
509
510
fn goto_type_of_expression_with_type_var_tuple ( ) {
510
- let test = goto_test (
511
+ let test = cursor_test (
511
512
r#"
512
513
type Alias[*Ts = ()] = tuple[*Ts<CURSOR>]
513
514
"# ,
@@ -521,7 +522,7 @@ mod tests {
521
522
522
523
#[ test]
523
524
fn goto_type_on_keyword_argument ( ) {
524
- let test = goto_test (
525
+ let test = cursor_test (
525
526
r#"
526
527
def test(a: str): ...
527
528
@@ -553,7 +554,7 @@ mod tests {
553
554
554
555
#[ test]
555
556
fn goto_type_on_incorrectly_typed_keyword_argument ( ) {
556
- let test = goto_test (
557
+ let test = cursor_test (
557
558
r#"
558
559
def test(a: str): ...
559
560
@@ -588,7 +589,7 @@ mod tests {
588
589
589
590
#[ test]
590
591
fn goto_type_on_kwargs ( ) {
591
- let test = goto_test (
592
+ let test = cursor_test (
592
593
r#"
593
594
def f(name: str): ...
594
595
@@ -622,14 +623,13 @@ f(**kwargs<CURSOR>)
622
623
623
624
#[ test]
624
625
fn goto_type_of_expression_with_builtin ( ) {
625
- let test = goto_test (
626
+ let test = cursor_test (
626
627
r#"
627
628
def foo(a: str):
628
629
a<CURSOR>
629
630
"# ,
630
631
) ;
631
632
632
- // FIXME: This should go to `str`
633
633
assert_snapshot ! ( test. goto_type_definition( ) , @r###"
634
634
info: lint:goto-type-definition: Type definition
635
635
--> stdlib/builtins.pyi:443:7
@@ -653,7 +653,7 @@ f(**kwargs<CURSOR>)
653
653
654
654
#[ test]
655
655
fn goto_type_definition_cursor_between_object_and_attribute ( ) {
656
- let test = goto_test (
656
+ let test = cursor_test (
657
657
r#"
658
658
class X:
659
659
def foo(a, b): ...
@@ -685,7 +685,7 @@ f(**kwargs<CURSOR>)
685
685
686
686
#[ test]
687
687
fn goto_between_call_arguments ( ) {
688
- let test = goto_test (
688
+ let test = cursor_test (
689
689
r#"
690
690
def foo(a, b): ...
691
691
@@ -715,7 +715,7 @@ f(**kwargs<CURSOR>)
715
715
716
716
#[ test]
717
717
fn goto_type_narrowing ( ) {
718
- let test = goto_test (
718
+ let test = cursor_test (
719
719
r#"
720
720
def foo(a: str | None, b):
721
721
if a is not None:
@@ -747,7 +747,7 @@ f(**kwargs<CURSOR>)
747
747
748
748
#[ test]
749
749
fn goto_type_none ( ) {
750
- let test = goto_test (
750
+ let test = cursor_test (
751
751
r#"
752
752
def foo(a: str | None, b):
753
753
a<CURSOR>
@@ -792,65 +792,7 @@ f(**kwargs<CURSOR>)
792
792
" ) ;
793
793
}
794
794
795
- fn goto_test ( source : & str ) -> GotoTest {
796
- let mut db = TestDb :: new ( ) ;
797
- let cursor_offset = source. find ( "<CURSOR>" ) . expect (
798
- "`source`` should contain a `<CURSOR>` marker, indicating the position of the cursor." ,
799
- ) ;
800
-
801
- let mut content = source[ ..cursor_offset] . to_string ( ) ;
802
- content. push_str ( & source[ cursor_offset + "<CURSOR>" . len ( ) ..] ) ;
803
-
804
- db. write_file ( "main.py" , & content)
805
- . expect ( "write to memory file system to be successful" ) ;
806
-
807
- let file = system_path_to_file ( & db, "main.py" ) . expect ( "newly written file to existing" ) ;
808
-
809
- Program :: from_settings (
810
- & db,
811
- ProgramSettings {
812
- python_version : PythonVersion :: latest ( ) ,
813
- python_platform : PythonPlatform :: default ( ) ,
814
- search_paths : SearchPathSettings {
815
- extra_paths : vec ! [ ] ,
816
- src_roots : vec ! [ SystemPathBuf :: from( "/" ) ] ,
817
- custom_typeshed : None ,
818
- python_path : PythonPath :: KnownSitePackages ( vec ! [ ] ) ,
819
- } ,
820
- } ,
821
- )
822
- . expect ( "Default settings to be valid" ) ;
823
-
824
- let mut insta_settings = insta:: Settings :: clone_current ( ) ;
825
- insta_settings. add_filter ( r#"\\(\w\w|\s|\.|")"# , "/$1" ) ;
826
-
827
- let insta_settings_guard = insta_settings. bind_to_scope ( ) ;
828
-
829
- GotoTest {
830
- db,
831
- cursor_offset : TextSize :: try_from ( cursor_offset)
832
- . expect ( "source to be smaller than 4GB" ) ,
833
- file,
834
- _insta_settings_guard : insta_settings_guard,
835
- }
836
- }
837
-
838
- struct GotoTest {
839
- db : TestDb ,
840
- cursor_offset : TextSize ,
841
- file : File ,
842
- _insta_settings_guard : SettingsBindDropGuard ,
843
- }
844
-
845
- impl GotoTest {
846
- fn write_file (
847
- & mut self ,
848
- path : impl AsRef < SystemPath > ,
849
- content : & str ,
850
- ) -> std:: io:: Result < ( ) > {
851
- self . db . write_file ( path, content)
852
- }
853
-
795
+ impl CursorTest {
854
796
fn goto_type_definition ( & self ) -> String {
855
797
let Some ( targets) = goto_type_definition ( & self . db , self . file , self . cursor_offset )
856
798
else {
@@ -861,19 +803,12 @@ f(**kwargs<CURSOR>)
861
803
return "No type definitions found" . to_string ( ) ;
862
804
}
863
805
864
- let mut buf = String :: new ( ) ;
865
-
866
806
let source = targets. range ;
867
-
868
- let config = DisplayDiagnosticConfig :: default ( )
869
- . color ( false )
870
- . format ( DiagnosticFormat :: Full ) ;
871
- for target in & * targets {
872
- let diag = GotoTypeDefinitionDiagnostic :: new ( source, target) . into_diagnostic ( ) ;
873
- write ! ( buf, "{}" , diag. display( & self . db, & config) ) . unwrap ( ) ;
874
- }
875
-
876
- buf
807
+ self . render_diagnostics (
808
+ targets
809
+ . into_iter ( )
810
+ . map ( |target| GotoTypeDefinitionDiagnostic :: new ( source, & target) ) ,
811
+ )
877
812
}
878
813
}
879
814
@@ -889,7 +824,9 @@ f(**kwargs<CURSOR>)
889
824
target : FileRange :: new ( target. file ( ) , target. focus_range ( ) ) ,
890
825
}
891
826
}
827
+ }
892
828
829
+ impl IntoDiagnostic for GotoTypeDefinitionDiagnostic {
893
830
fn into_diagnostic ( self ) -> Diagnostic {
894
831
let mut source = SubDiagnostic :: new ( Severity :: Info , "Source" ) ;
895
832
source. annotate ( Annotation :: primary (
0 commit comments