diff --git a/crates/ty_ide/src/goto_declaration.rs b/crates/ty_ide/src/goto_declaration.rs index cdb2cd31ed..2e25862f59 100644 --- a/crates/ty_ide/src/goto_declaration.rs +++ b/crates/ty_ide/src/goto_declaration.rs @@ -1355,6 +1355,462 @@ class MyClass: "#); } + #[test] + fn goto_declaration_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_declaration(), @r" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: str): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + "); + } + + #[test] + fn goto_declaration_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_declaration(), @r#" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab("hello") + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: str): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab("hello") + | ^^ + | + "#); + } + + #[test] + fn goto_declaration_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_declaration(), @r" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int, b: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, 2) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: int): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, 2) + | ^^ + | + "); + } + + #[test] + fn goto_declaration_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_declaration(), @r" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int, b: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: int): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + "); + } + + #[test] + fn goto_declaration_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_declaration(), @r" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, b=2) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: int, *, b: int): ... + | ^^ + 9 | + 10 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, b=2) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:11:5 + | + 10 | @overload + 11 | def ab(a: int, *, c: int): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, b=2) + | ^^ + | + "); + } + + #[test] + fn goto_declaration_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_declaration(), @r" + info[goto-declaration]: Declaration + --> mymodule.pyi:5:5 + | + 4 | @overload + 5 | def ab(a: int): ... + | ^^ + 6 | + 7 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, c=2) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:8:5 + | + 7 | @overload + 8 | def ab(a: int, *, b: int): ... + | ^^ + 9 | + 10 | @overload + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, c=2) + | ^^ + | + + info[goto-declaration]: Declaration + --> mymodule.pyi:11:5 + | + 10 | @overload + 11 | def ab(a: int, *, c: int): ... + | ^^ + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, c=2) + | ^^ + | + "); + } + impl CursorTest { fn goto_declaration(&self) -> String { let Some(targets) = goto_declaration(&self.db, self.cursor.file, self.cursor.offset) diff --git a/crates/ty_ide/src/goto_definition.rs b/crates/ty_ide/src/goto_definition.rs index 3c5edac5ec..55156488fc 100644 --- a/crates/ty_ide/src/goto_definition.rs +++ b/crates/ty_ide/src/goto_definition.rs @@ -802,6 +802,318 @@ my_func(my_other_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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + "#); + } + + #[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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab("hello") + | ^^ + | + "#); + } + + #[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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a, b = None): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, 2) + | ^^ + | + "#); + } + + #[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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a, b = None): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^^ + | + "#); + } + + #[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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a, *, b = None, c = None): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, b=2) + | ^^ + | + "#); + } + + #[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]: Definition + --> mymodule.py:2:5 + | + 2 | def ab(a, *, b = None, c = None): + | ^^ + 3 | """the real implementation!""" + | + info: Source + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, c=2) + | ^^ + | + "#); + } + struct GotoDefinitionDiagnostic { source: FileRange, target: FileRange, diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 90b64d911e..fbfc57041e 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -791,7 +791,453 @@ mod tests { } #[test] - fn hover_overload() { + fn hover_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): + """the int overload""" + +@overload +def ab(a: str): ... + """the str overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r" + (a: int) -> Unknown + (a: str) -> Unknown + --------------------------------------------- + the int overload + + --------------------------------------------- + ```python + (a: int) -> Unknown + (a: str) -> Unknown + ``` + --- + ```text + the int overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^- + | || + | |Cursor offset + | source + | + "); + } + + #[test] + fn hover_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): + """the int overload""" + +@overload +def ab(a: str): + """the str overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r#" + (a: int) -> Unknown + (a: str) -> Unknown + --------------------------------------------- + the int overload + + --------------------------------------------- + ```python + (a: int) -> Unknown + (a: str) -> Unknown + ``` + --- + ```text + the int overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab("hello") + | ^- + | || + | |Cursor offset + | source + | + "#); + } + + #[test] + fn hover_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): + """the two arg overload""" + +@overload +def ab(a: int): + """the one arg overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r" + ( + a: int, + b: int + ) -> Unknown + (a: int) -> Unknown + --------------------------------------------- + the two arg overload + + --------------------------------------------- + ```python + ( + a: int, + b: int + ) -> Unknown + (a: int) -> Unknown + ``` + --- + ```text + the two arg overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, 2) + | ^- + | || + | |Cursor offset + | source + | + "); + } + + #[test] + fn hover_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): + """the two arg overload""" + +@overload +def ab(a: int): + """the one arg overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r" + ( + a: int, + b: int + ) -> Unknown + (a: int) -> Unknown + --------------------------------------------- + the two arg overload + + --------------------------------------------- + ```python + ( + a: int, + b: int + ) -> Unknown + (a: int) -> Unknown + ``` + --- + ```text + the two arg overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1) + | ^- + | || + | |Cursor offset + | source + | + "); + } + + #[test] + fn hover_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): + """keywordless overload""" + +@overload +def ab(a: int, *, b: int): + """b overload""" + +@overload +def ab(a: int, *, c: int): + """c overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r" + (a: int) -> Unknown + ( + a: int, + *, + b: int + ) -> Unknown + ( + a: int, + *, + c: int + ) -> Unknown + --------------------------------------------- + keywordless overload + + --------------------------------------------- + ```python + (a: int) -> Unknown + ( + a: int, + *, + b: int + ) -> Unknown + ( + a: int, + *, + c: int + ) -> Unknown + ``` + --- + ```text + keywordless overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, b=2) + | ^- + | || + | |Cursor offset + | source + | + "); + } + + #[test] + fn hover_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): + """keywordless overload""" + +@overload +def ab(a: int, *, b: int): + """b overload""" + +@overload +def ab(a: int, *, c: int): + """c overload""" +"#, + ) + .build(); + + assert_snapshot!(test.hover(), @r" + (a: int) -> Unknown + ( + a: int, + *, + b: int + ) -> Unknown + ( + a: int, + *, + c: int + ) -> Unknown + --------------------------------------------- + keywordless overload + + --------------------------------------------- + ```python + (a: int) -> Unknown + ( + a: int, + *, + b: int + ) -> Unknown + ( + a: int, + *, + c: int + ) -> Unknown + ``` + --- + ```text + keywordless overload + + ``` + --------------------------------------------- + info[hover]: Hovered content is + --> main.py:4:1 + | + 2 | from mymodule import ab + 3 | + 4 | ab(1, c=2) + | ^- + | || + | |Cursor offset + | source + | + "); + } + + #[test] + fn hover_overload_ambiguous() { let test = cursor_test( r#" from typing import overload @@ -858,7 +1304,7 @@ mod tests { } #[test] - fn hover_overload_compact() { + fn hover_overload_ambiguous_compact() { let test = cursor_test( r#" from typing import overload diff --git a/crates/ty_ide/src/signature_help.rs b/crates/ty_ide/src/signature_help.rs index e9f775e6d6..bbcb676c69 100644 --- a/crates/ty_ide/src/signature_help.rs +++ b/crates/ty_ide/src/signature_help.rs @@ -258,6 +258,9 @@ fn create_parameters_from_offsets( #[cfg(test)] mod tests { + use insta::assert_snapshot; + + use crate::MarkupKind; use crate::docstring::Docstring; use crate::signature_help::SignatureHelpInfo; use crate::tests::{CursorTest, cursor_test}; @@ -470,6 +473,354 @@ mod tests { assert_eq!(param2.name, "value"); } + #[test] + fn signature_help_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): + """the int overload""" + +@overload +def ab(a: str): ... + """the str overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int) -> Unknown + --------------------------------------------- + the int overload + + -------------- active parameter ------------- + a: int + --------------------------------------------- + + =============== other signature ============= + (a: str) -> Unknown + --------------------------------------------- + the real implementation! + + -------------- active parameter ------------- + a: str + --------------------------------------------- + "); + } + + #[test] + fn signature_help_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): + """the int overload""" + +@overload +def ab(a: str): + """the str overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int) -> Unknown + --------------------------------------------- + the int overload + + -------------- active parameter ------------- + a: int + --------------------------------------------- + + =============== other signature ============= + (a: str) -> Unknown + --------------------------------------------- + the str overload + + -------------- active parameter ------------- + a: str + --------------------------------------------- + "); + } + + #[test] + fn signature_help_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): + """the two arg overload""" + +@overload +def ab(a: int): + """the one arg overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int, b: int) -> Unknown + --------------------------------------------- + the two arg overload + + -------------- active parameter ------------- + b: int + --------------------------------------------- + + =============== other signature ============= + (a: int) -> Unknown + --------------------------------------------- + the one arg overload + + (no active parameter specified) + "); + } + + #[test] + fn signature_help_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): + """the two arg overload""" + +@overload +def ab(a: int): + """the one arg overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int, b: int) -> Unknown + --------------------------------------------- + the two arg overload + + -------------- active parameter ------------- + a: int + --------------------------------------------- + + =============== other signature ============= + (a: int) -> Unknown + --------------------------------------------- + the one arg overload + + -------------- active parameter ------------- + a: int + --------------------------------------------- + "); + } + + #[test] + fn signature_help_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): + """keywordless overload""" + +@overload +def ab(a: int, *, b: int): + """b overload""" + +@overload +def ab(a: int, *, c: int): + """c overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int, *, b: int) -> Unknown + --------------------------------------------- + b overload + + -------------- active parameter ------------- + b: int + --------------------------------------------- + + =============== other signature ============= + (a: int) -> Unknown + --------------------------------------------- + keywordless overload + + (no active parameter specified) + =============== other signature ============= + (a: int, *, c: int) -> Unknown + --------------------------------------------- + c overload + + -------------- active parameter ------------- + c: int + --------------------------------------------- + "); + } + + #[test] + fn signature_help_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): + """keywordless overload""" + +@overload +def ab(a: int, *, b: int): + """b overload""" + +@overload +def ab(a: int, *, c: int): + """c overload""" +"#, + ) + .build(); + + assert_snapshot!(test.signature_help_render(), @r" + ============== active signature ============= + (a: int, *, c: int) -> Unknown + --------------------------------------------- + c overload + + -------------- active parameter ------------- + c: int + --------------------------------------------- + + =============== other signature ============= + (a: int) -> Unknown + --------------------------------------------- + keywordless overload + + (no active parameter specified) + =============== other signature ============= + (a: int, *, b: int) -> Unknown + --------------------------------------------- + b overload + + -------------- active parameter ------------- + b: int + --------------------------------------------- + "); + } + #[test] fn signature_help_class_constructor() { let test = cursor_test( @@ -826,5 +1177,94 @@ mod tests { fn signature_help(&self) -> Option { crate::signature_help::signature_help(&self.db, self.cursor.file, self.cursor.offset) } + + fn signature_help_render(&self) -> String { + use std::fmt::Write; + + let Some(signature_help) = self.signature_help() else { + return "Signature help found no signatures".to_string(); + }; + let active_sig_heading = "\n============== active signature =============\n"; + let second_sig_heading = "\n=============== other signature =============\n"; + let active_arg_heading = "\n-------------- active parameter -------------\n"; + + let mut buf = String::new(); + if let Some(active_signature) = signature_help.active_signature { + let signature = signature_help + .signatures + .get(active_signature) + .expect("failed to find active signature!"); + write!( + &mut buf, + "{heading}{label}{line}{docs}", + heading = active_sig_heading, + label = signature.label, + line = MarkupKind::PlainText.horizontal_line(), + docs = signature + .documentation + .as_ref() + .map(Docstring::render_plaintext) + .unwrap_or_default(), + ) + .unwrap(); + if let Some(active_parameter) = signature.active_parameter { + let parameter = signature + .parameters + .get(active_parameter) + .expect("failed to find active parameter!"); + write!( + &mut buf, + "{heading}{label}{line}{docs}", + heading = active_arg_heading, + label = parameter.label, + line = MarkupKind::PlainText.horizontal_line(), + docs = parameter.documentation.as_deref().unwrap_or_default(), + ) + .unwrap(); + } else { + writeln!(&mut buf, "\n(no active parameter specified)").unwrap(); + } + } else { + writeln!(&mut buf, "\n(no active signature specified)").unwrap(); + } + + for (idx, signature) in signature_help.signatures.iter().enumerate() { + if Some(idx) == signature_help.active_signature { + continue; + } + write!( + &mut buf, + "{heading}{label}{line}{docs}", + heading = second_sig_heading, + label = signature.label, + line = MarkupKind::PlainText.horizontal_line(), + docs = signature + .documentation + .as_ref() + .map(Docstring::render_plaintext) + .unwrap_or_default(), + ) + .unwrap(); + if let Some(active_parameter) = signature.active_parameter { + let parameter = signature + .parameters + .get(active_parameter) + .expect("failed to find active parameter!"); + write!( + &mut buf, + "{heading}{label}{line}{docs}", + heading = active_arg_heading, + label = parameter.label, + line = MarkupKind::PlainText.horizontal_line(), + docs = parameter.documentation.as_deref().unwrap_or_default(), + ) + .unwrap(); + } else { + write!(&mut buf, "\n(no active parameter specified)").unwrap(); + } + } + + buf + } } }