use crate::goto::find_goto_target; use crate::{Db, NavigationTargets, RangedValue}; use ruff_db::files::{File, FileRange}; use ruff_db::parsed::parsed_module; use ruff_text_size::{Ranged, TextSize}; use ty_python_semantic::{ImportAliasResolution, SemanticModel}; /// Navigate to the definition of a symbol. /// /// A "definition" is the actual implementation of a symbol, potentially in a source file /// rather than a stub file. This differs from "declaration" which may navigate to stub files. /// When possible, this function will map from stub file declarations to their corresponding /// source file implementations using the `StubMapper`. pub fn goto_definition( db: &dyn Db, file: File, offset: TextSize, ) -> Option> { let module = parsed_module(db, file).load(db); let model = SemanticModel::new(db, file); let goto_target = find_goto_target(&model, &module, offset)?; let definition_targets = goto_target .get_definition_targets(&model, ImportAliasResolution::ResolveAliases)? .definition_targets(db)?; Some(RangedValue { range: FileRange::new(file, goto_target.range()), value: definition_targets, }) } #[cfg(test)] pub(super) mod test { use crate::tests::{CursorTest, IntoDiagnostic}; use crate::{NavigationTargets, RangedValue, goto_definition}; use insta::assert_snapshot; use ruff_db::diagnostic::{ Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic, SubDiagnosticSeverity, }; use ruff_text_size::Ranged; /// goto-definition on a module should go to the .py not the .pyi /// /// TODO: this currently doesn't work right! This is especially surprising /// because [`goto_definition_stub_map_module_ref`] works fine. #[test] fn goto_definition_stub_map_module_import() { let test = CursorTest::builder() .source( "main.py", " from mymodule import my_function ", ) .source( "mymodule.py", r#" def my_function(): return "hello" "#, ) .source( "mymodule.pyi", r#" def my_function(): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:2:6 | 2 | from mymodule import my_function | ^^^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:1:1 | 1 | | - 2 | def my_function(): 3 | return "hello" | "#); } /// goto-definition on a module ref should go to the .py not the .pyi #[test] fn goto_definition_stub_map_module_ref() { let test = CursorTest::builder() .source( "main.py", " import mymodule x = mymodule ", ) .source( "mymodule.py", r#" def my_function(): return "hello" "#, ) .source( "mymodule.pyi", r#" def my_function(): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:3:5 | 2 | import mymodule 3 | x = mymodule | ^^^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:1:1 | 1 | | - 2 | def my_function(): 3 | return "hello" | "#); } /// goto-definition on a function call should go to the .py not the .pyi #[test] fn goto_definition_stub_map_function() { let test = CursorTest::builder() .source( "main.py", " from mymodule import my_function print(my_function()) ", ) .source( "mymodule.py", r#" def my_function(): return "hello" def other_function(): return "other" "#, ) .source( "mymodule.pyi", r#" def my_function(): ... def other_function(): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:3:7 | 2 | from mymodule import my_function 3 | print(my_function()) | ^^^^^^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def my_function(): | ----------- 3 | return "hello" | "#); } /// goto-definition on a function definition in a .pyi should go to the .py #[test] fn goto_definition_stub_map_function_def() { let test = CursorTest::builder() .source( "mymodule.py", r#" def my_function(): return "hello" def other_function(): return "other" "#, ) .source( "mymodule.pyi", r#" def my_function(): ... def other_function(): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> mymodule.pyi:2:5 | 2 | def my_function(): ... | ^^^^^^^^^^^ Clicking here 3 | 4 | def other_function(): ... | info: Found 1 definition --> mymodule.py:2:5 | 2 | def my_function(): | ----------- 3 | return "hello" | "#); } /// goto-definition on a function that's redefined many times in the impl .py /// /// Currently this yields all instances. There's an argument for only yielding /// the final one since that's the one "exported" but, this is consistent for /// how we do file-local goto-definition. #[test] fn goto_definition_stub_map_function_redefine() { let test = CursorTest::builder() .source( "main.py", " from mymodule import my_function print(my_function()) ", ) .source( "mymodule.py", r#" def my_function(): return "hello" def my_function(): return "hello again" def my_function(): return "we can't keep doing this" def other_function(): return "other" "#, ) .source( "mymodule.pyi", r#" def my_function(): ... def other_function(): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:3:7 | 2 | from mymodule import my_function 3 | print(my_function()) | ^^^^^^^^^^^ Clicking here | info: Found 3 definitions --> mymodule.py:2:5 | 2 | def my_function(): | ----------- 3 | return "hello" 4 | 5 | def my_function(): | ----------- 6 | return "hello again" 7 | 8 | def my_function(): | ----------- 9 | return "we can't keep doing this" | "#); } /// goto-definition on a class ref go to the .py not the .pyi #[test] fn goto_definition_stub_map_class_ref() { let test = CursorTest::builder() .source( "main.py", " from mymodule import MyClass x = MyClass ", ) .source( "mymodule.py", r#" class MyClass: def __init__(self, val): self.val = val class MyOtherClass: def __init__(self, val): self.val = val + 1 "#, ) .source( "mymodule.pyi", r#" class MyClass: def __init__(self, val: bool): ... class MyOtherClass: def __init__(self, val: bool): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:3:5 | 2 | from mymodule import MyClass 3 | x = MyClass | ^^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:2:7 | 2 | class MyClass: | ------- 3 | def __init__(self, val): 4 | self.val = val | "); } /// goto-definition on a class def in a .pyi should go to the .py #[test] fn goto_definition_stub_map_class_def() { let test = CursorTest::builder() .source( "mymodule.py", r#" class MyClass: def __init__(self, val): self.val = val class MyOtherClass: def __init__(self, val): self.val = val + 1 "#, ) .source( "mymodule.pyi", r#" class MyClass: def __init__(self, val: bool): ... class MyOtherClass: def __init__(self, val: bool): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> mymodule.pyi:2:7 | 2 | class MyClass: | ^^^^^^^ Clicking here 3 | def __init__(self, val: bool): ... | info: Found 1 definition --> mymodule.py:2:7 | 2 | class MyClass: | ------- 3 | def __init__(self, val): 4 | self.val = val | "); } /// goto-definition on a class init should go to the .py not the .pyi #[test] fn goto_definition_stub_map_class_init() { let test = CursorTest::builder() .source( "main.py", " from mymodule import MyClass x = MyClass(0) ", ) .source( "mymodule.py", r#" class MyClass: def __init__(self, val): self.val = val class MyOtherClass: def __init__(self, val): self.val = val + 1 "#, ) .source( "mymodule.pyi", r#" class MyClass: def __init__(self, val: bool): ... class MyOtherClass: def __init__(self, val: bool): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:3:5 | 2 | from mymodule import MyClass 3 | x = MyClass(0) | ^^^^^^^ Clicking here | info: Found 2 definitions --> mymodule.py:2:7 | 2 | class MyClass: | ------- 3 | def __init__(self, val): | -------- 4 | self.val = val | "); } /// goto-definition on a class method should go to the .py not the .pyi #[test] fn goto_definition_stub_map_class_method() { let test = CursorTest::builder() .source( "main.py", " from mymodule import MyClass x = MyClass(0) x.action() ", ) .source( "mymodule.py", r#" class MyClass: def __init__(self, val): self.val = val def action(self): print(self.val) class MyOtherClass: def __init__(self, val): self.val = val + 1 "#, ) .source( "mymodule.pyi", r#" class MyClass: def __init__(self, val: bool): ... def action(self): ... class MyOtherClass: def __init__(self, val: bool): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:4:3 | 2 | from mymodule import MyClass 3 | x = MyClass(0) 4 | x.action() | ^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:5:9 | 3 | def __init__(self, val): 4 | self.val = val 5 | def action(self): | ------ 6 | print(self.val) | "); } /// goto-definition on a class function should go to the .py not the .pyi #[test] fn goto_definition_stub_map_class_function() { let test = CursorTest::builder() .source( "main.py", " from mymodule import MyClass x = MyClass.action() ", ) .source( "mymodule.py", r#" class MyClass: def __init__(self, val): self.val = val def action(): print("hi!") class MyOtherClass: def __init__(self, val): self.val = val + 1 "#, ) .source( "mymodule.pyi", r#" class MyClass: def __init__(self, val: bool): ... def action(): ... class MyOtherClass: def __init__(self, val: bool): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:3:13 | 2 | from mymodule import MyClass 3 | x = MyClass.action() | ^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:5:9 | 3 | def __init__(self, val): 4 | self.val = val 5 | def action(): | ------ 6 | print("hi!") | "#); } /// goto-definition on a class import should go to the .py not the .pyi #[test] fn goto_definition_stub_map_class_import() { let test = CursorTest::builder() .source( "main.py", " from mymodule import MyClass ", ) .source( "mymodule.py", r#" class MyClass: ... "#, ) .source( "mymodule.pyi", r#" class MyClass: ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:2:22 | 2 | from mymodule import MyClass | ^^^^^^^ Clicking here | info: Found 1 definition --> mymodule.py:2:7 | 2 | class MyClass: ... | ------- | "); } /// goto-definition on a nested call using a keyword arg where both funcs have that arg name /// /// In this case they ultimately have different signatures. #[test] fn goto_definition_nested_keyword_arg1() { let test = CursorTest::builder() .source( "main.py", r#" def my_func(ab, y, z = None): ... def my_other_func(ab, y): ... my_other_func(my_func(ab=5, y=2), 0) my_func(my_other_func(ab=5, y=2), 0) "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:5:23 | 3 | def my_other_func(ab, y): ... 4 | 5 | my_other_func(my_func(ab=5, y=2), 0) | ^^ Clicking here 6 | my_func(my_other_func(ab=5, y=2), 0) | info: Found 1 definition --> main.py:2:13 | 2 | def my_func(ab, y, z = None): ... | -- 3 | def my_other_func(ab, y): ... | "); } /// goto-definition on a nested call using a keyword arg where both funcs have that arg name /// /// In this case they ultimately have different signatures. #[test] fn goto_definition_nested_keyword_arg2() { let test = CursorTest::builder() .source( "main.py", r#" def my_func(ab, y, z = None): ... def my_other_func(ab, y): ... my_other_func(my_func(ab=5, y=2), 0) my_func(my_other_func(ab=5, y=2), 0) "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:6:23 | 5 | my_other_func(my_func(ab=5, y=2), 0) 6 | my_func(my_other_func(ab=5, y=2), 0) | ^^ Clicking here | info: Found 1 definition --> main.py:3:19 | 2 | def my_func(ab, y, z = None): ... 3 | def my_other_func(ab, y): ... | -- 4 | 5 | my_other_func(my_func(ab=5, y=2), 0) | "); } /// goto-definition on a nested call using a keyword arg where both funcs have that arg name /// /// In this case they have identical signatures. #[test] fn goto_definition_nested_keyword_arg3() { let test = CursorTest::builder() .source( "main.py", r#" def my_func(ab, y): ... def my_other_func(ab, y): ... my_other_func(my_func(ab=5, y=2), 0) my_func(my_other_func(ab=5, y=2), 0) "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:5:23 | 3 | def my_other_func(ab, y): ... 4 | 5 | my_other_func(my_func(ab=5, y=2), 0) | ^^ Clicking here 6 | my_func(my_other_func(ab=5, y=2), 0) | info: Found 1 definition --> main.py:2:13 | 2 | def my_func(ab, y): ... | -- 3 | def my_other_func(ab, y): ... | "); } /// goto-definition on a nested call using a keyword arg where both funcs have that arg name /// /// In this case they have identical signatures. #[test] fn goto_definition_nested_keyword_arg4() { let test = CursorTest::builder() .source( "main.py", r#" def my_func(ab, y): ... def my_other_func(ab, y): ... my_other_func(my_func(ab=5, y=2), 0) my_func(my_other_func(ab=5, y=2), 0) "#, ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:6:23 | 5 | my_other_func(my_func(ab=5, y=2), 0) 6 | my_func(my_other_func(ab=5, y=2), 0) | ^^ Clicking here | info: Found 1 definition --> main.py:3:19 | 2 | def my_func(ab, y): ... 3 | def my_other_func(ab, y): ... | -- 4 | 5 | my_other_func(my_func(ab=5, y=2), 0) | "); } #[test] fn goto_definition_overload_type_disambiguated1() { let test = CursorTest::builder() .source( "main.py", " from mymodule import ab ab(1) ", ) .source( "mymodule.py", r#" def ab(a): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int): ... @overload def ab(a: str): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab(1) | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_overload_type_disambiguated2() { let test = CursorTest::builder() .source( "main.py", r#" from mymodule import ab ab("hello") "#, ) .source( "mymodule.py", r#" def ab(a): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int): ... @overload def ab(a: str): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab("hello") | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_overload_arity_disambiguated1() { let test = CursorTest::builder() .source( "main.py", " from mymodule import ab ab(1, 2) ", ) .source( "mymodule.py", r#" def ab(a, b = None): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int, b: int): ... @overload def ab(a: int): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab(1, 2) | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a, b = None): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_overload_arity_disambiguated2() { let test = CursorTest::builder() .source( "main.py", " from mymodule import ab ab(1) ", ) .source( "mymodule.py", r#" def ab(a, b = None): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int, b: int): ... @overload def ab(a: int): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab(1) | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a, b = None): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_overload_keyword_disambiguated1() { let test = CursorTest::builder() .source( "main.py", " from mymodule import ab ab(1, b=2) ", ) .source( "mymodule.py", r#" def ab(a, *, b = None, c = None): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int): ... @overload def ab(a: int, *, b: int): ... @overload def ab(a: int, *, c: int): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab(1, b=2) | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a, *, b = None, c = None): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_overload_keyword_disambiguated2() { let test = CursorTest::builder() .source( "main.py", " from mymodule import ab ab(1, c=2) ", ) .source( "mymodule.py", r#" def ab(a, *, b = None, c = None): """the real implementation!""" "#, ) .source( "mymodule.pyi", r#" from typing import overload @overload def ab(a: int): ... @overload def ab(a: int, *, b: int): ... @overload def ab(a: int, *, c: int): ... "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:4:1 | 2 | from mymodule import ab 3 | 4 | ab(1, c=2) | ^^ Clicking here | info: Found 1 definition --> mymodule.py:2:5 | 2 | def ab(a, *, b = None, c = None): | -- 3 | """the real implementation!""" | "#); } #[test] fn goto_definition_binary_operator() { let test = CursorTest::builder() .source( "main.py", " class Test: def __add__(self, other): return Test() a = Test() b = Test() a + b ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:10:3 | 8 | b = Test() 9 | 10 | a + b | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __add__(self, other): | ------- 4 | return Test() | "); } #[test] fn goto_definition_binary_operator_reflected_dunder() { let test = CursorTest::builder() .source( "main.py", " class A: def __radd__(self, other) -> A: return self class B: ... B() + A() ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:8:5 | 6 | class B: ... 7 | 8 | B() + A() | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class A: 3 | def __radd__(self, other) -> A: | -------- 4 | return self | "); } #[test] fn goto_definition_binary_operator_no_spaces_before_operator() { let test = CursorTest::builder() .source( "main.py", " class Test: def __add__(self, other): return Test() a = Test() b = Test() a+b ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:10:2 | 8 | b = Test() 9 | 10 | a+b | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __add__(self, other): | ------- 4 | return Test() | "); } #[test] fn goto_definition_binary_operator_no_spaces_after_operator() { let test = CursorTest::builder() .source( "main.py", " class Test: def __add__(self, other): return Test() a = Test() b = Test() a+b ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:10:3 | 8 | b = Test() 9 | 10 | a+b | ^ Clicking here | info: Found 1 definition --> main.py:8:1 | 7 | a = Test() 8 | b = Test() | - 9 | 10 | a+b | "); } #[test] fn goto_definition_binary_operator_comment() { let test = CursorTest::builder() .source( "main.py", " class Test: def __add__(self, other): return Test() ( Test() # comment + Test() ) ", ) .build(); assert_snapshot!(test.goto_definition(), @"No goto target found"); } #[test] fn goto_definition_unary_operator() { let test = CursorTest::builder() .source( "main.py", " class Test: def __invert__(self) -> 'Test': ... a = Test() ~a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | ~a | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __invert__(self) -> 'Test': ... | ---------- 4 | 5 | a = Test() | "); } /// We jump to the `__invert__` definition here even though its signature is incorrect. #[test] fn goto_definition_unary_operator_with_bad_dunder_definition() { let test = CursorTest::builder() .source( "main.py", " class Test: def __invert__(self, extra_arg) -> 'Test': ... a = Test() ~a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | ~a | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __invert__(self, extra_arg) -> 'Test': ... | ---------- 4 | 5 | a = Test() | "); } #[test] fn goto_definition_unary_after_operator() { let test = CursorTest::builder() .source( "main.py", " class Test: def __invert__(self) -> 'Test': ... a = Test() ~ a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | ~ a | ^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __invert__(self) -> 'Test': ... | ---------- 4 | 5 | a = Test() | "); } #[test] fn goto_definition_unary_between_operator_and_operand() { let test = CursorTest::builder() .source( "main.py", " class Test: def __invert__(self) -> 'Test': ... a = Test() -a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:2 | 5 | a = Test() 6 | 7 | -a | ^ Clicking here | info: Found 1 definition --> main.py:5:1 | 3 | def __invert__(self) -> 'Test': ... 4 | 5 | a = Test() | - 6 | 7 | -a | "); } #[test] fn goto_definition_unary_not_with_dunder_bool() { let test = CursorTest::builder() .source( "main.py", " class Test: def __bool__(self) -> bool: ... a = Test() not a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | not a | ^^^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __bool__(self) -> bool: ... | -------- 4 | 5 | a = Test() | "); } #[test] fn goto_definition_unary_not_with_dunder_len() { let test = CursorTest::builder() .source( "main.py", " class Test: def __len__(self) -> 42: ... a = Test() not a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | not a | ^^^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __len__(self) -> 42: ... | ------- 4 | 5 | a = Test() | "); } /// If `__bool__` is defined incorrectly, `not` does not fallback to `__len__`. /// Instead, we jump to the `__bool__` definition as usual. /// The fallback only occurs if `__bool__` is not defined at all. #[test] fn goto_definition_unary_not_with_bad_dunder_bool_and_dunder_len() { let test = CursorTest::builder() .source( "main.py", " class Test: def __bool__(self, extra_arg) -> bool: ... def __len__(self) -> 42: ... a = Test() not a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:8:1 | 6 | a = Test() 7 | 8 | not a | ^^^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __bool__(self, extra_arg) -> bool: ... | -------- 4 | def __len__(self) -> 42: ... | "); } /// Same as for unary operators that only use a single dunder, /// we still jump to `__len__` for `not` goto-definition even if /// the `__len__` signature is incorrect (but only if there is no /// `__bool__` definition). #[test] fn goto_definition_unary_not_with_no_dunder_bool_and_bad_dunder_len() { let test = CursorTest::builder() .source( "main.py", " class Test: def __len__(self, extra_arg) -> 42: ... a = Test() not a ", ) .build(); assert_snapshot!(test.goto_definition(), @r" info[goto-definition]: Go to definition --> main.py:7:1 | 5 | a = Test() 6 | 7 | not a | ^^^ Clicking here | info: Found 1 definition --> main.py:3:9 | 2 | class Test: 3 | def __len__(self, extra_arg) -> 42: ... | ------- 4 | 5 | a = Test() | "); } #[test] fn float_annotation() { let test = CursorTest::builder() .source( "main.py", " a: float = 3.14 ", ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:2:4 | 2 | a: float = 3.14 | ^^^^^ Clicking here | info: Found 2 definitions --> stdlib/builtins.pyi:348:7 | 347 | @disjoint_base 348 | class int: | --- 349 | """int([x]) -> integer 350 | int(x, base=10) -> integer | ::: stdlib/builtins.pyi:661:7 | 660 | @disjoint_base 661 | class float: | ----- 662 | """Convert a string or number to a floating-point number, if possible.""" | "#); } #[test] fn complex_annotation() { let test = CursorTest::builder() .source( "main.py", " a: complex = 3.14 ", ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:2:4 | 2 | a: complex = 3.14 | ^^^^^^^ Clicking here | info: Found 3 definitions --> stdlib/builtins.pyi:348:7 | 347 | @disjoint_base 348 | class int: | --- 349 | """int([x]) -> integer 350 | int(x, base=10) -> integer | ::: stdlib/builtins.pyi:661:7 | 660 | @disjoint_base 661 | class float: | ----- 662 | """Convert a string or number to a floating-point number, if possible.""" | ::: stdlib/builtins.pyi:822:7 | 821 | @disjoint_base 822 | class complex: | ------- 823 | """Create a complex number from a string or numbers. | "#); } /// Regression test for . /// We must ensure we respect re-import convention for stub files for /// imports in builtins.pyi. #[test] fn goto_definition_unimported_symbol_imported_in_builtins() { let test = CursorTest::builder() .source( "main.py", " TracebackType ", ) .build(); assert_snapshot!(test.goto_definition(), @"No goto target found"); } // TODO: Should only list `a: int` #[test] fn redeclarations() { let test = CursorTest::builder() .source( "main.py", r#" a: str = "test" a: int = 10 print(a) a: bool = True "#, ) .build(); assert_snapshot!(test.goto_definition(), @r#" info[goto-definition]: Go to definition --> main.py:6:7 | 4 | a: int = 10 5 | 6 | print(a) | ^ Clicking here 7 | 8 | a: bool = True | info: Found 3 definitions --> main.py:2:1 | 2 | a: str = "test" | - 3 | 4 | a: int = 10 | - 5 | 6 | print(a) 7 | 8 | a: bool = True | - | "#); } impl CursorTest { fn goto_definition(&self) -> String { let Some(targets) = salsa::attach(&self.db, || { goto_definition(&self.db, self.cursor.file, self.cursor.offset) }) else { return "No goto target found".to_string(); }; if targets.is_empty() { return "No definitions found".to_string(); } self.render_diagnostics([GotoDiagnostic::new(GotoAction::Definition, targets)]) } } pub(crate) struct GotoDiagnostic { action: GotoAction, targets: RangedValue, } impl GotoDiagnostic { pub(crate) fn new(action: GotoAction, targets: RangedValue) -> Self { Self { action, targets } } } impl IntoDiagnostic for GotoDiagnostic { fn into_diagnostic(self) -> Diagnostic { let source = self.targets.range; let mut main = Diagnostic::new( DiagnosticId::Lint(LintName::of(self.action.name())), Severity::Info, self.action.label().to_string(), ); main.annotate( Annotation::primary(Span::from(source.file()).with_range(source.range())) .message("Clicking here"), ); let mut sub = SubDiagnostic::new( SubDiagnosticSeverity::Info, format_args!( "Found {} {}{}", self.targets.len(), self.action.item_label(), if self.targets.len() == 1 { "" } else { "s" } ), ); for target in self.targets { sub.annotate(Annotation::secondary( Span::from(target.file()).with_range(target.focus_range()), )); } main.sub(sub); main } } pub(crate) enum GotoAction { Definition, Declaration, TypeDefinition, } impl GotoAction { fn name(&self) -> &'static str { match self { GotoAction::Definition => "goto-definition", GotoAction::Declaration => "goto-declaration", GotoAction::TypeDefinition => "goto-type definition", } } fn label(&self) -> &'static str { match self { GotoAction::Definition => "Go to definition", GotoAction::Declaration => "Go to declaration", GotoAction::TypeDefinition => "Go to type definition", } } fn item_label(&self) -> &'static str { match self { GotoAction::Definition => "definition", GotoAction::Declaration => "declaration", GotoAction::TypeDefinition => "type definition", } } } }