From f40a0b38000c73fcc09c3a675e5dca95c209fc4e Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Tue, 2 Sep 2025 10:38:26 -0400 Subject: [PATCH] [ty] add more lsp tests for overloads (#20148) I decided to split out the addition of these tests from other PRs so that it's easier to follow changes to the LSP's function call handling. I'm not particularly concerned with whether the results produced by these tests are "good" or "bad" in this PR, I'm just establishing a baseline. --- crates/ty_ide/src/goto_declaration.rs | 456 ++++++++++++++++++++++++++ crates/ty_ide/src/goto_definition.rs | 312 ++++++++++++++++++ crates/ty_ide/src/hover.rs | 450 ++++++++++++++++++++++++- crates/ty_ide/src/signature_help.rs | 440 +++++++++++++++++++++++++ 4 files changed, 1656 insertions(+), 2 deletions(-) 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 + } } }