diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 7e1e123228..15201b032a 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -1,6 +1,6 @@ use std::{fmt, vec}; -use crate::{Db, NavigationTarget}; +use crate::{Db, HasNavigationTargets, NavigationTarget}; use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal}; @@ -19,10 +19,48 @@ pub struct InlayHint { impl InlayHint { fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self { - let label_parts = vec![ - ": ".into(), - InlayHintLabelPart::new(ty.display(db).to_string()), - ]; + // Render the type to a string, and get subspans for all the types that make it up + let details = ty.display(db).to_string_parts(); + + // Ok so the idea here is that we potentially have a random soup of spans here, + // and each byte of the string can have at most one target associate with it. + // Thankfully, they were generally pushed in print order, with the inner smaller types + // appearing before the outer bigger ones. + // + // So we record where we are in the string, and every time we find a type, we + // check if it's further along in the string. If it is, great, we give it the + // span for its range, and then advance where we are. + let mut offset = 0; + let mut label_parts = vec![": ".into()]; + for (target, detail) in details.targets.iter().zip(&details.details) { + match detail { + ty_python_semantic::types::TypeDetail::Type(ty) => { + let start = target.start().to_usize(); + let end = target.end().to_usize(); + // If we skipped over some bytes, push them with no target + if start > offset { + label_parts.push(details.label[offset..start].into()); + } + // Ok, this is the first type that claimed these bytes, give it the target + if start >= offset { + let target = ty.navigation_targets(db).into_iter().next(); + label_parts.push( + InlayHintLabelPart::new(&details.label[start..end]).with_target(target), + ); + offset = end; + } + } + ty_python_semantic::types::TypeDetail::SignatureStart + | ty_python_semantic::types::TypeDetail::SignatureEnd + | ty_python_semantic::types::TypeDetail::Parameter(_) => { + // Don't care about these + } + } + } + // "flush" the rest of the label without any target + if offset < details.label.len() { + label_parts.push(details.label[offset..details.label.len()].into()); + } Self { position, @@ -451,7 +489,7 @@ mod tests { /// [`inlay_hints_with_settings`] to generate hints with custom settings. /// /// [`inlay_hints_with_settings`]: Self::inlay_hints_with_settings - fn inlay_hints(&self) -> String { + fn inlay_hints(&mut self) -> String { self.inlay_hints_with_settings(&InlayHintSettings { variable_types: true, call_argument_names: true, @@ -463,40 +501,44 @@ mod tests { } /// Returns the inlay hints for the given test case with custom settings. - fn inlay_hints_with_settings(&self, settings: &InlayHintSettings) -> String { + fn inlay_hints_with_settings(&mut self, settings: &InlayHintSettings) -> String { let hints = inlay_hints(&self.db, self.file, self.range, settings); let mut buf = source_text(&self.db, self.file).as_str().to_string(); - let mut diagnostics = Vec::new(); + let mut tbd_diagnostics = Vec::new(); let mut offset = 0; for hint in hints { + let end_position = hint.position.to_usize() + offset; let mut hint_str = "[".to_string(); - let end_position = (hint.position.to_u32() as usize) + offset; - for part in hint.label.parts() { - hint_str.push_str(part.text()); - - if let Some(target) = part.target() { - let label_range = TextRange::at(hint.position, TextSize::ZERO); - - let label_file_range = FileRange::new(self.file, label_range); - - diagnostics - .push(InlayHintLocationDiagnostic::new(label_file_range, target)); + if let Some(target) = part.target().cloned() { + let part_position = u32::try_from(end_position + hint_str.len()).unwrap(); + let part_len = u32::try_from(part.text().len()).unwrap(); + let label_range = + TextRange::at(TextSize::new(part_position), TextSize::new(part_len)); + tbd_diagnostics.push((label_range, target)); } + hint_str.push_str(part.text()); } hint_str.push(']'); - offset += hint_str.len(); buf.insert_str(end_position, &hint_str); } + self.db.write_file("main2.py", &buf).unwrap(); + let inlayed_file = + system_path_to_file(&self.db, "main2.py").expect("newly written file to existing"); + + let diagnostics = tbd_diagnostics.into_iter().map(|(label_range, target)| { + InlayHintLocationDiagnostic::new(FileRange::new(inlayed_file, label_range), &target) + }); + let mut rendered_diagnostics = self.render_diagnostics(diagnostics); if !rendered_diagnostics.is_empty() { @@ -528,13 +570,16 @@ mod tests { write!(buf, "{}", diag.display(&self.db, &config)).unwrap(); } - buf + // Windows path normalization for typeshed references + // "hey why is \x08 getting clobbered to /x08?" + // no it's not I don't know what you're talking about + buf.replace('\\', "/") } } #[test] fn test_assign_statement() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -546,7 +591,7 @@ mod tests { ", ); - assert_snapshot!(test.inlay_hints(), @r" + assert_snapshot!(test.inlay_hints(), @r#" def i(x: int, /) -> int: return x @@ -554,12 +599,50 @@ mod tests { y[: Literal[1]] = x z[: int] = i(1) w[: int] = z - "); + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:7:5 + | + 5 | x = 1 + 6 | y[: Literal[1]] = x + 7 | z[: int] = i(1) + | ^^^ + 8 | w[: int] = z + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:5 + | + 6 | y[: Literal[1]] = x + 7 | z[: int] = i(1) + 8 | w[: int] = z + | ^^^ + | + "#); } #[test] fn test_unpacked_tuple_assignment() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -583,12 +666,87 @@ mod tests { x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) x3[: int], y3[: str] = (i(1), s('abc')) x4[: int], y4[: str] = (x3, y3) + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:6 + | + 7 | x1, y1 = (1, 'abc') + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + | ^^^ + 10 | x4[: int], y4[: str] = (x3, y3) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:17 + | + 7 | x1, y1 = (1, 'abc') + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + | ^^^ + 10 | x4[: int], y4[: str] = (x3, y3) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:6 + | + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + 10 | x4[: int], y4[: str] = (x3, y3) + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:17 + | + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = (x1, y1) + 9 | x3[: int], y3[: str] = (i(1), s('abc')) + 10 | x4[: int], y4[: str] = (x3, y3) + | ^^^ + | "#); } #[test] fn test_multiple_assignment() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -612,12 +770,87 @@ mod tests { x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 x3[: int], y3[: str] = i(1), s('abc') x4[: int], y4[: str] = x3, y3 + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:6 + | + 7 | x1, y1 = 1, 'abc' + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + 9 | x3[: int], y3[: str] = i(1), s('abc') + | ^^^ + 10 | x4[: int], y4[: str] = x3, y3 + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:17 + | + 7 | x1, y1 = 1, 'abc' + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + 9 | x3[: int], y3[: str] = i(1), s('abc') + | ^^^ + 10 | x4[: int], y4[: str] = x3, y3 + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:6 + | + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + 9 | x3[: int], y3[: str] = i(1), s('abc') + 10 | x4[: int], y4[: str] = x3, y3 + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:17 + | + 8 | x2[: Literal[1]], y2[: Literal["abc"]] = x1, y1 + 9 | x3[: int], y3[: str] = i(1), s('abc') + 10 | x4[: int], y4[: str] = x3, y3 + | ^^^ + | "#); } #[test] fn test_tuple_assignment() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -641,12 +874,87 @@ mod tests { y[: tuple[Literal[1], Literal["abc"]]] = x z[: tuple[int, str]] = (i(1), s('abc')) w[: tuple[int, str]] = z + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:11 + | + 7 | x = (1, 'abc') + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + | ^^^ + 10 | w[: tuple[int, str]] = z + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:16 + | + 7 | x = (1, 'abc') + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + | ^^^ + 10 | w[: tuple[int, str]] = z + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:11 + | + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + 10 | w[: tuple[int, str]] = z + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:16 + | + 8 | y[: tuple[Literal[1], Literal["abc"]]] = x + 9 | z[: tuple[int, str]] = (i(1), s('abc')) + 10 | w[: tuple[int, str]] = z + | ^^^ + | "#); } #[test] fn test_nested_tuple_assignment() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -669,12 +977,123 @@ mod tests { x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:6 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + | ^^^ + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:18 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + | ^^^ + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:29 + | + 7 | x1, (y1, z1) = (1, ('abc', 2)) + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + | ^^^ + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:6 + | + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:18 + | + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:29 + | + 8 | x2[: Literal[1]], (y2[: Literal["abc"]], z2[: Literal[2]]) = (x1, (y1, z1)) + 9 | x3[: int], (y3[: str], z3[: int]) = (i(1), (s('abc'), i(2))) + 10 | x4[: int], (y4[: str], z4[: int]) = (x3, (y3, z3)) + | ^^^ + | "#); } #[test] fn test_assign_statement_with_type_annotation() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -685,7 +1104,7 @@ mod tests { w = z", ); - assert_snapshot!(test.inlay_hints(), @r" + assert_snapshot!(test.inlay_hints(), @r#" def i(x: int, /) -> int: return x @@ -693,12 +1112,30 @@ mod tests { y[: Literal[1]] = x z: int = i(1) w[: int] = z - "); + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:5 + | + 6 | y[: Literal[1]] = x + 7 | z: int = i(1) + 8 | w[: int] = z + | ^^^ + | + "#); } #[test] fn test_assign_statement_out_of_range() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -706,17 +1143,36 @@ mod tests { z = x", ); - assert_snapshot!(test.inlay_hints(), @r" + assert_snapshot!(test.inlay_hints(), @r#" def i(x: int, /) -> int: return x x[: int] = i(1) z = x - "); + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:4:5 + | + 2 | def i(x: int, /) -> int: + 3 | return x + 4 | x[: int] = i(1) + | ^^^ + 5 | z = x + | + "#); } #[test] fn test_assign_attribute_of_instance() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class A: def __init__(self, y): @@ -728,7 +1184,7 @@ mod tests { ", ); - assert_snapshot!(test.inlay_hints(), @r" + assert_snapshot!(test.inlay_hints(), @r#" class A: def __init__(self, y): self.x[: int] = int(1) @@ -738,6 +1194,43 @@ mod tests { a.y[: int] = int(3) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:4:18 + | + 2 | class A: + 3 | def __init__(self, y): + 4 | self.x[: int] = int(1) + | ^^^ + 5 | self.y[: Unknown] = y + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class A: + | ^ + 3 | def __init__(self, y): + 4 | self.x = int(1) + | + info: Source + --> main2.py:7:5 + | + 5 | self.y[: Unknown] = y + 6 | + 7 | a[: A] = A([y=]2) + | ^ + 8 | a.y[: int] = int(3) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:3:24 | @@ -748,20 +1241,37 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:7:7 + --> main2.py:7:13 | - 5 | self.y = y + 5 | self.y[: Unknown] = y 6 | - 7 | a = A(2) - | ^ - 8 | a.y = int(3) + 7 | a[: A] = A([y=]2) + | ^ + 8 | a.y[: int] = int(3) | - "); + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:7 + | + 7 | a[: A] = A([y=]2) + 8 | a.y[: int] = int(3) + | ^^^ + | + "#); } #[test] fn test_many_literals() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" a = 1 b = 1.0 @@ -794,7 +1304,7 @@ mod tests { #[test] fn test_many_literals_tuple() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" a = (1, 2) b = (1.0, 2.0) @@ -827,7 +1337,7 @@ mod tests { #[test] fn test_many_literals_unpacked_tuple() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" a1, a2 = (1, 2) b1, b2 = (1.0, 2.0) @@ -860,7 +1370,7 @@ mod tests { #[test] fn test_many_literals_multiple() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" a1, a2 = 1, 2 b1, b2 = 1.0, 2.0 @@ -893,7 +1403,7 @@ mod tests { #[test] fn test_many_literals_list() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" a = [1, 2] b = [1.0, 2.0] @@ -921,12 +1431,463 @@ mod tests { i[: list[Unknown | bytes]] = [b'\x01', b'\x02'] j[: list[Unknown | int | float]] = [+1, +2.0] k[: list[Unknown | int | float]] = [-1, -2.0] + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:2:5 + | + 2 | a[: list[Unknown | int]] = [1, 2] + | ^^^^ + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:2:20 + | + 2 | a[: list[Unknown | int]] = [1, 2] + | ^^^ + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:3:5 + | + 2 | a[: list[Unknown | int]] = [1, 2] + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + | ^^^^ + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:661:7 + | + 660 | @disjoint_base + 661 | class float: + | ^^^^^ + 662 | """Convert a string or number to a floating-point number, if possible.""" + | + info: Source + --> main2.py:3:20 + | + 2 | a[: list[Unknown | int]] = [1, 2] + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + | ^^^^^ + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:4:5 + | + 2 | a[: list[Unknown | int]] = [1, 2] + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + | ^^^^ + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2591:7 + | + 2590 | @final + 2591 | class bool(int): + | ^^^^ + 2592 | """Returns True when the argument is true, False otherwise. + 2593 | The builtins True and False are the only two instances of the class bool. + | + info: Source + --> main2.py:4:20 + | + 2 | a[: list[Unknown | int]] = [1, 2] + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + | ^^^^ + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:5:5 + | + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + | ^^^^ + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/types.pyi:950:11 + | + 948 | if sys.version_info >= (3, 10): + 949 | @final + 950 | class NoneType: + | ^^^^^^^^ + 951 | """The type of the None singleton.""" + | + info: Source + --> main2.py:5:20 + | + 3 | b[: list[Unknown | float]] = [1.0, 2.0] + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + | ^^^^ + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:6:5 + | + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + | ^^^^ + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:6:20 + | + 4 | c[: list[Unknown | bool]] = [True, False] + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + | ^^^ + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:7:5 + | + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + | ^^^^ + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:7:20 + | + 5 | d[: list[Unknown | None]] = [None, None] + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + | ^^^ + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:8:5 + | + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + | ^^^^ + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:8:20 + | + 6 | e[: list[Unknown | str]] = ["hel", "lo"] + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + | ^^^ + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:9:5 + | + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + | ^^^^ + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/string/templatelib.pyi:10:7 + | + 9 | @final + 10 | class Template: # TODO: consider making `Template` generic on `TypeVarTuple` + | ^^^^^^^^ + 11 | """Template object""" + | + info: Source + --> main2.py:9:20 + | + 7 | f[: list[Unknown | str]] = ['the', 're'] + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + | ^^^^^^^^ + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:10:5 + | + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + | ^^^^ + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:1448:7 + | + 1447 | @disjoint_base + 1448 | class bytes(Sequence[int]): + | ^^^^^ + 1449 | """bytes(iterable_of_ints) -> bytes + 1450 | bytes(string, encoding[, errors]) -> bytes + | + info: Source + --> main2.py:10:20 + | + 8 | g[: list[Unknown | str]] = [f"{ft}", f"{ft}"] + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + | ^^^^^ + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:11:5 + | + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + | ^^^^ + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:11:20 + | + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + | ^^^ + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:661:7 + | + 660 | @disjoint_base + 661 | class float: + | ^^^^^ + 662 | """Convert a string or number to a floating-point number, if possible.""" + | + info: Source + --> main2.py:11:26 + | + 9 | h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"] + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + | ^^^^^ + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:12:5 + | + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | ^^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:12:20 + | + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:661:7 + | + 660 | @disjoint_base + 661 | class float: + | ^^^^^ + 662 | """Convert a string or number to a floating-point number, if possible.""" + | + info: Source + --> main2.py:12:26 + | + 10 | i[: list[Unknown | bytes]] = [b'/x01', b'/x02'] + 11 | j[: list[Unknown | int | float]] = [+1, +2.0] + 12 | k[: list[Unknown | int | float]] = [-1, -2.0] + | ^^^^^ + | "#); } #[test] fn test_simple_init_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" class MyClass: def __init__(self): @@ -948,12 +1909,138 @@ mod tests { y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) a[: MyClass], b[: MyClass] = MyClass(), MyClass() c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + + --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:6:5 + | + 4 | self.x: int = 1 + 5 | + 6 | x[: MyClass] = MyClass() + | ^^^^^^^ + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:7:11 + | + 6 | x[: MyClass] = MyClass() + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + | ^^^^^^^ + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:7:20 + | + 6 | x[: MyClass] = MyClass() + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + | ^^^^^^^ + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:8:5 + | + 6 | x[: MyClass] = MyClass() + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + | ^^^^^^^ + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:8:19 + | + 6 | x[: MyClass] = MyClass() + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + | ^^^^^^^ + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:9:5 + | + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | ^^^^^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass: + | ^^^^^^^ + 3 | def __init__(self): + 4 | self.x: int = 1 + | + info: Source + --> main2.py:9:19 + | + 7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass()) + 8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass() + 9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass()) + | ^^^^^^^ + | "); } #[test] fn test_generic_init_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( r#" class MyClass[T, U]: def __init__(self, x: list[T], y: tuple[U, U]): @@ -980,43 +2067,80 @@ mod tests { --------------------------------------------- info[inlay-hint-location]: Inlay Hint Target - --> main.py:3:24 + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:4:18 | 2 | class MyClass[T, U]: 3 | def __init__(self, x: list[T], y: tuple[U, U]): - | ^ + 4 | self.x[: list[T@MyClass]] = x + | ^^^^ + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): 4 | self.x = x - 5 | self.y = y | info: Source - --> main.py:7:13 + --> main2.py:7:5 | - 5 | self.y = y + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y 6 | - 7 | x = MyClass([42], ("a", "b")) - | ^ - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + | ^^^^^^^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… | info[inlay-hint-location]: Inlay Hint Target - --> main.py:3:36 - | - 2 | class MyClass[T, U]: - 3 | def __init__(self, x: list[T], y: tuple[U, U]): - | ^ - 4 | self.x = x - 5 | self.y = y - | + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | info: Source - --> main.py:7:19 + --> main2.py:7:23 | - 5 | self.y = y + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y 6 | - 7 | x = MyClass([42], ("a", "b")) - | ^ - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + | ^^^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:7:28 + | + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y + 6 | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + | ^^^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… | info[inlay-hint-location]: Inlay Hint Target @@ -1029,14 +2153,15 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:8:14 - | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | + --> main2.py:7:45 + | + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y + 6 | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + | ^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | info[inlay-hint-location]: Inlay Hint Target --> main.py:3:36 @@ -1048,13 +2173,126 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:8:20 + --> main2.py:7:55 + | + 5 | self.y[: tuple[U@MyClass, U@MyClass]] = y + 6 | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + | ^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:8:11 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^^^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:29 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:8:34 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:8:40 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^^^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:8:58 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:8:63 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^^^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1067,13 +2305,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:8:41 + --> main2.py:8:82 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1086,13 +2324,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:8:47 + --> main2.py:8:92 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1105,13 +2343,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:9:16 + --> main2.py:8:117 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - | ^ - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1124,13 +2362,125 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:9:22 + --> main2.py:8:127 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - | ^ - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + | ^ + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:9:5 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^^^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:23 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:28 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:9:39 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^^^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:9:57 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:9:62 + | + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^^^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1143,13 +2493,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:9:43 + --> main2.py:9:79 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - | ^ - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1162,13 +2512,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:9:49 + --> main2.py:9:89 | - 7 | x = MyClass([42], ("a", "b")) - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - | ^ - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1181,12 +2531,13 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:10:17 + --> main2.py:9:114 | - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… | info[inlay-hint-location]: Inlay Hint Target @@ -1199,12 +2550,119 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:10:23 + --> main2.py:9:124 | - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ + 7 | x[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")) + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + | ^ + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:10:5 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^^^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:23 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:28 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class MyClass[T, U]: + | ^^^^^^^ + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + 4 | self.x = x + | + info: Source + --> main2.py:10:39 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^^^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:10:57 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:915:7 + | + 914 | @disjoint_base + 915 | class str(Sequence[str]): + | ^^^ + 916 | """str(object='') -> str + 917 | str(bytes_or_buffer[, encoding[, errors]]) -> str + | + info: Source + --> main2.py:10:62 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^^^ | info[inlay-hint-location]: Inlay Hint Target @@ -1217,12 +2675,12 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:10:44 + --> main2.py:10:80 | - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -1235,19 +2693,55 @@ mod tests { 5 | self.y = y | info: Source - --> main.py:10:50 + --> main2.py:10:90 | - 8 | y = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - 9 | a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b")) - 10 | c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))) - | ^ + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:3:24 + | + 2 | class MyClass[T, U]: + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + | ^ + 4 | self.x = x + 5 | self.y = y + | + info: Source + --> main2.py:10:115 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^ + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:3:36 + | + 2 | class MyClass[T, U]: + 3 | def __init__(self, x: list[T], y: tuple[U, U]): + | ^ + 4 | self.x = x + 5 | self.y = y + | + info: Source + --> main2.py:10:125 + | + 8 | y[: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a",… + 9 | a[: MyClass[Unknown | int, str]], b[: MyClass[Unknown | int, str]] = MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "b… + 10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "… + | ^ | "#); } #[test] fn test_disabled_variable_types() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def i(x: int, /) -> int: return x @@ -1272,7 +2766,7 @@ mod tests { #[test] fn test_function_call_with_positional_or_keyword_parameter() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass foo(1)", @@ -1290,18 +2784,18 @@ mod tests { 3 | foo(1) | info: Source - --> main.py:3:5 + --> main2.py:3:6 | 2 | def foo(x: int): pass - 3 | foo(1) - | ^ + 3 | foo([x=]1) + | ^ | "); } #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_name() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass x = 1 @@ -1326,19 +2820,19 @@ mod tests { 4 | y = 2 | info: Source - --> main.py:6:5 + --> main2.py:6:6 | 4 | y = 2 5 | foo(x) - 6 | foo(y) - | ^ + 6 | foo([x=]y) + | ^ | "); } #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass class MyClass: @@ -1362,6 +2856,26 @@ mod tests { foo(val.x) foo([x=]val.y) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:3:7 + | + 2 | def foo(x: int): pass + 3 | class MyClass: + | ^^^^^^^ + 4 | def __init__(self): + 5 | self.x: int = 1 + | + info: Source + --> main2.py:7:7 + | + 5 | self.x: int = 1 + 6 | self.y: int = 2 + 7 | val[: MyClass] = MyClass() + | ^^^^^^^ + 8 | + 9 | foo(val.x) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:2:9 | @@ -1371,11 +2885,11 @@ mod tests { 4 | def __init__(self): | info: Source - --> main.py:10:5 + --> main2.py:10:6 | 9 | foo(val.x) - 10 | foo(val.y) - | ^ + 10 | foo([x=]val.y) + | ^ | "); } @@ -1383,7 +2897,7 @@ mod tests { #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_attribute_not() { // This one checks that we don't allow elide `x=` for `x.y` - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass class MyClass: @@ -1407,6 +2921,26 @@ mod tests { foo(x.x) foo([x=]x.y) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:3:7 + | + 2 | def foo(x: int): pass + 3 | class MyClass: + | ^^^^^^^ + 4 | def __init__(self): + 5 | self.x: int = 1 + | + info: Source + --> main2.py:7:5 + | + 5 | self.x: int = 1 + 6 | self.y: int = 2 + 7 | x[: MyClass] = MyClass() + | ^^^^^^^ + 8 | + 9 | foo(x.x) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:2:9 | @@ -1416,18 +2950,18 @@ mod tests { 4 | def __init__(self): | info: Source - --> main.py:10:5 + --> main2.py:10:6 | 9 | foo(x.x) - 10 | foo(x.y) - | ^ + 10 | foo([x=]x.y) + | ^ | "); } #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass class MyClass: @@ -1455,6 +2989,26 @@ mod tests { foo(val.x()) foo([x=]val.y()) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:3:7 + | + 2 | def foo(x: int): pass + 3 | class MyClass: + | ^^^^^^^ + 4 | def __init__(self): + 5 | def x() -> int: + | + info: Source + --> main2.py:9:7 + | + 7 | def y() -> int: + 8 | return 2 + 9 | val[: MyClass] = MyClass() + | ^^^^^^^ + 10 | + 11 | foo(val.x()) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:2:9 | @@ -1464,18 +3018,18 @@ mod tests { 4 | def __init__(self): | info: Source - --> main.py:12:5 + --> main2.py:12:6 | 11 | foo(val.x()) - 12 | foo(val.y()) - | ^ + 12 | foo([x=]val.y()) + | ^ | "); } #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_complex() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " from typing import List @@ -1507,6 +3061,26 @@ mod tests { foo(val.x()[0]) foo([x=]val.y()[1]) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> main.py:5:7 + | + 4 | def foo(x: int): pass + 5 | class MyClass: + | ^^^^^^^ + 6 | def __init__(self): + 7 | def x() -> List[int]: + | + info: Source + --> main2.py:11:7 + | + 9 | def y() -> List[int]: + 10 | return 2 + 11 | val[: MyClass] = MyClass() + | ^^^^^^^ + 12 | + 13 | foo(val.x()[0]) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:4:9 | @@ -1518,18 +3092,18 @@ mod tests { 6 | def __init__(self): | info: Source - --> main.py:14:5 + --> main2.py:14:6 | 13 | foo(val.x()[0]) - 14 | foo(val.y()[1]) - | ^ + 14 | foo([x=]val.y()[1]) + | ^ | "); } #[test] fn test_function_call_with_positional_or_keyword_parameter_redundant_subscript() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass x = [1] @@ -1539,7 +3113,7 @@ mod tests { foo(y[0])", ); - assert_snapshot!(test.inlay_hints(), @r" + assert_snapshot!(test.inlay_hints(), @r#" def foo(x: int): pass x[: list[Unknown | int]] = [1] y[: list[Unknown | int]] = [2] @@ -1547,6 +3121,80 @@ mod tests { foo(x[0]) foo([x=]y[0]) --------------------------------------------- + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:3:5 + | + 2 | def foo(x: int): pass + 3 | x[: list[Unknown | int]] = [1] + | ^^^^ + 4 | y[: list[Unknown | int]] = [2] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:3:20 + | + 2 | def foo(x: int): pass + 3 | x[: list[Unknown | int]] = [1] + | ^^^ + 4 | y[: list[Unknown | int]] = [2] + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:2802:7 + | + 2801 | @disjoint_base + 2802 | class list(MutableSequence[_T]): + | ^^^^ + 2803 | """Built-in mutable sequence. + | + info: Source + --> main2.py:4:5 + | + 2 | def foo(x: int): pass + 3 | x[: list[Unknown | int]] = [1] + 4 | y[: list[Unknown | int]] = [2] + | ^^^^ + 5 | + 6 | foo(x[0]) + | + + info[inlay-hint-location]: Inlay Hint Target + --> stdlib/builtins.pyi:348:7 + | + 347 | @disjoint_base + 348 | class int: + | ^^^ + 349 | """int([x]) -> integer + 350 | int(x, base=10) -> integer + | + info: Source + --> main2.py:4:20 + | + 2 | def foo(x: int): pass + 3 | x[: list[Unknown | int]] = [1] + 4 | y[: list[Unknown | int]] = [2] + | ^^^ + 5 | + 6 | foo(x[0]) + | + info[inlay-hint-location]: Inlay Hint Target --> main.py:2:9 | @@ -1556,18 +3204,18 @@ mod tests { 4 | y = [2] | info: Source - --> main.py:7:5 + --> main2.py:7:6 | 6 | foo(x[0]) - 7 | foo(y[0]) - | ^ + 7 | foo([x=]y[0]) + | ^ | - "); + "#); } #[test] fn test_function_call_with_positional_only_parameter() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, /): pass foo(1)", @@ -1581,7 +3229,7 @@ mod tests { #[test] fn test_function_call_with_variadic_parameter() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(*args: int): pass foo(1)", @@ -1595,7 +3243,7 @@ mod tests { #[test] fn test_function_call_with_keyword_variadic_parameter() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(**kwargs: int): pass foo(x=1)", @@ -1609,7 +3257,7 @@ mod tests { #[test] fn test_function_call_with_keyword_only_parameter() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(*, x: int): pass foo(x=1)", @@ -1623,7 +3271,7 @@ mod tests { #[test] fn test_function_call_positional_only_and_positional_or_keyword_parameters() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, /, y: int): pass foo(1, 2)", @@ -1641,18 +3289,18 @@ mod tests { 3 | foo(1, 2) | info: Source - --> main.py:3:8 + --> main2.py:3:9 | 2 | def foo(x: int, /, y: int): pass - 3 | foo(1, 2) - | ^ + 3 | foo(1, [y=]2) + | ^ | "); } #[test] fn test_function_call_positional_only_and_variadic_parameters() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, /, *args: int): pass foo(1, 2, 3)", @@ -1666,7 +3314,7 @@ mod tests { #[test] fn test_function_call_positional_only_and_keyword_variadic_parameters() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, /, **kwargs: int): pass foo(1, x=2)", @@ -1680,7 +3328,7 @@ mod tests { #[test] fn test_class_constructor_call_init() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class Foo: def __init__(self, x: int): pass @@ -1704,13 +3352,30 @@ mod tests { 5 | f = Foo(1) | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | class Foo: 3 | def __init__(self, x: int): pass + 4 | Foo([x=]1) + | ^ + 5 | f[: Foo] = Foo([x=]1) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class Foo: + | ^^^ + 3 | def __init__(self, x: int): pass 4 | Foo(1) - | ^ - 5 | f = Foo(1) + | + info: Source + --> main2.py:5:5 + | + 3 | def __init__(self, x: int): pass + 4 | Foo([x=]1) + 5 | f[: Foo] = Foo([x=]1) + | ^^^ | info[inlay-hint-location]: Inlay Hint Target @@ -1723,19 +3388,19 @@ mod tests { 5 | f = Foo(1) | info: Source - --> main.py:5:9 + --> main2.py:5:17 | 3 | def __init__(self, x: int): pass - 4 | Foo(1) - 5 | f = Foo(1) - | ^ + 4 | Foo([x=]1) + 5 | f[: Foo] = Foo([x=]1) + | ^ | "); } #[test] fn test_class_constructor_call_new() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class Foo: def __new__(cls, x: int): pass @@ -1759,13 +3424,30 @@ mod tests { 5 | f = Foo(1) | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | class Foo: 3 | def __new__(cls, x: int): pass + 4 | Foo([x=]1) + | ^ + 5 | f[: Foo] = Foo([x=]1) + | + + info[inlay-hint-location]: Inlay Hint Target + --> main.py:2:7 + | + 2 | class Foo: + | ^^^ + 3 | def __new__(cls, x: int): pass 4 | Foo(1) - | ^ - 5 | f = Foo(1) + | + info: Source + --> main2.py:5:5 + | + 3 | def __new__(cls, x: int): pass + 4 | Foo([x=]1) + 5 | f[: Foo] = Foo([x=]1) + | ^^^ | info[inlay-hint-location]: Inlay Hint Target @@ -1778,19 +3460,19 @@ mod tests { 5 | f = Foo(1) | info: Source - --> main.py:5:9 + --> main2.py:5:17 | 3 | def __new__(cls, x: int): pass - 4 | Foo(1) - 5 | f = Foo(1) - | ^ + 4 | Foo([x=]1) + 5 | f[: Foo] = Foo([x=]1) + | ^ | "); } #[test] fn test_class_constructor_call_meta_class_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class MetaFoo: def __call__(self, x: int): pass @@ -1816,19 +3498,19 @@ mod tests { 5 | pass | info: Source - --> main.py:6:5 + --> main2.py:6:6 | 4 | class Foo(metaclass=MetaFoo): 5 | pass - 6 | Foo(1) - | ^ + 6 | Foo([x=]1) + | ^ | "); } #[test] fn test_callable_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " from typing import Callable def foo(x: Callable[[int], int]): @@ -1844,7 +3526,7 @@ mod tests { #[test] fn test_instance_method_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class Foo: def bar(self, y: int): pass @@ -1865,19 +3547,19 @@ mod tests { 4 | Foo().bar(2) | info: Source - --> main.py:4:11 + --> main2.py:4:12 | 2 | class Foo: 3 | def bar(self, y: int): pass - 4 | Foo().bar(2) - | ^ + 4 | Foo().bar([y=]2) + | ^ | "); } #[test] fn test_class_method_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class Foo: @classmethod @@ -1901,19 +3583,19 @@ mod tests { 5 | Foo.bar(2) | info: Source - --> main.py:5:9 + --> main2.py:5:10 | 3 | @classmethod 4 | def bar(cls, y: int): pass - 5 | Foo.bar(2) - | ^ + 5 | Foo.bar([y=]2) + | ^ | "); } #[test] fn test_static_method_call() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class Foo: @staticmethod @@ -1937,19 +3619,19 @@ mod tests { 5 | Foo.bar(2) | info: Source - --> main.py:5:9 + --> main2.py:5:10 | 3 | @staticmethod 4 | def bar(y: int): pass - 5 | Foo.bar(2) - | ^ + 5 | Foo.bar([y=]2) + | ^ | "); } #[test] fn test_function_call_with_union_type() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int | str): pass foo(1) @@ -1970,12 +3652,12 @@ mod tests { 4 | foo('abc') | info: Source - --> main.py:3:5 + --> main2.py:3:6 | 2 | def foo(x: int | str): pass - 3 | foo(1) - | ^ - 4 | foo('abc') + 3 | foo([x=]1) + | ^ + 4 | foo([x=]'abc') | info[inlay-hint-location]: Inlay Hint Target @@ -1987,19 +3669,19 @@ mod tests { 4 | foo('abc') | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | def foo(x: int | str): pass - 3 | foo(1) - 4 | foo('abc') - | ^ + 3 | foo([x=]1) + 4 | foo([x=]'abc') + | ^ | "); } #[test] fn test_function_call_multiple_positional_arguments() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, y: str, z: bool): pass foo(1, 'hello', True)", @@ -2017,11 +3699,11 @@ mod tests { 3 | foo(1, 'hello', True) | info: Source - --> main.py:3:5 + --> main2.py:3:6 | 2 | def foo(x: int, y: str, z: bool): pass - 3 | foo(1, 'hello', True) - | ^ + 3 | foo([x=]1, [y=]'hello', [z=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2032,11 +3714,11 @@ mod tests { 3 | foo(1, 'hello', True) | info: Source - --> main.py:3:8 + --> main2.py:3:13 | 2 | def foo(x: int, y: str, z: bool): pass - 3 | foo(1, 'hello', True) - | ^ + 3 | foo([x=]1, [y=]'hello', [z=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2047,18 +3729,18 @@ mod tests { 3 | foo(1, 'hello', True) | info: Source - --> main.py:3:17 + --> main2.py:3:26 | 2 | def foo(x: int, y: str, z: bool): pass - 3 | foo(1, 'hello', True) - | ^ + 3 | foo([x=]1, [y=]'hello', [z=]True) + | ^ | "); } #[test] fn test_function_call_mixed_positional_and_keyword() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, y: str, z: bool): pass foo(1, z=True, y='hello')", @@ -2076,18 +3758,18 @@ mod tests { 3 | foo(1, z=True, y='hello') | info: Source - --> main.py:3:5 + --> main2.py:3:6 | 2 | def foo(x: int, y: str, z: bool): pass - 3 | foo(1, z=True, y='hello') - | ^ + 3 | foo([x=]1, z=True, y='hello') + | ^ | "); } #[test] fn test_function_call_with_default_parameters() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int, y: str = 'default', z: bool = False): pass foo(1) @@ -2110,13 +3792,13 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:3:5 + --> main2.py:3:6 | 2 | def foo(x: int, y: str = 'default', z: bool = False): pass - 3 | foo(1) - | ^ - 4 | foo(1, 'custom') - 5 | foo(1, 'custom', True) + 3 | foo([x=]1) + | ^ + 4 | foo([x=]1, [y=]'custom') + 5 | foo([x=]1, [y=]'custom', [z=]True) | info[inlay-hint-location]: Inlay Hint Target @@ -2128,13 +3810,13 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | def foo(x: int, y: str = 'default', z: bool = False): pass - 3 | foo(1) - 4 | foo(1, 'custom') - | ^ - 5 | foo(1, 'custom', True) + 3 | foo([x=]1) + 4 | foo([x=]1, [y=]'custom') + | ^ + 5 | foo([x=]1, [y=]'custom', [z=]True) | info[inlay-hint-location]: Inlay Hint Target @@ -2146,13 +3828,13 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:4:8 + --> main2.py:4:13 | 2 | def foo(x: int, y: str = 'default', z: bool = False): pass - 3 | foo(1) - 4 | foo(1, 'custom') - | ^ - 5 | foo(1, 'custom', True) + 3 | foo([x=]1) + 4 | foo([x=]1, [y=]'custom') + | ^ + 5 | foo([x=]1, [y=]'custom', [z=]True) | info[inlay-hint-location]: Inlay Hint Target @@ -2164,12 +3846,12 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:5:5 + --> main2.py:5:6 | - 3 | foo(1) - 4 | foo(1, 'custom') - 5 | foo(1, 'custom', True) - | ^ + 3 | foo([x=]1) + 4 | foo([x=]1, [y=]'custom') + 5 | foo([x=]1, [y=]'custom', [z=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2181,12 +3863,12 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:5:8 + --> main2.py:5:13 | - 3 | foo(1) - 4 | foo(1, 'custom') - 5 | foo(1, 'custom', True) - | ^ + 3 | foo([x=]1) + 4 | foo([x=]1, [y=]'custom') + 5 | foo([x=]1, [y=]'custom', [z=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2198,19 +3880,19 @@ mod tests { 4 | foo(1, 'custom') | info: Source - --> main.py:5:18 + --> main2.py:5:27 | - 3 | foo(1) - 4 | foo(1, 'custom') - 5 | foo(1, 'custom', True) - | ^ + 3 | foo([x=]1) + 4 | foo([x=]1, [y=]'custom') + 5 | foo([x=]1, [y=]'custom', [z=]True) + | ^ | "); } #[test] fn test_nested_function_calls() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int) -> int: return x * 2 @@ -2245,12 +3927,12 @@ mod tests { 10 | baz(foo(5), bar(bar('test')), True) | info: Source - --> main.py:10:5 + --> main2.py:10:6 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2261,12 +3943,12 @@ mod tests { 3 | return x * 2 | info: Source - --> main.py:10:9 + --> main2.py:10:14 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2280,12 +3962,12 @@ mod tests { 10 | baz(foo(5), bar(bar('test')), True) | info: Source - --> main.py:10:13 + --> main2.py:10:22 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2298,12 +3980,12 @@ mod tests { 6 | return y | info: Source - --> main.py:10:17 + --> main2.py:10:30 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2316,12 +3998,12 @@ mod tests { 6 | return y | info: Source - --> main.py:10:21 + --> main2.py:10:38 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2335,19 +4017,19 @@ mod tests { 10 | baz(foo(5), bar(bar('test')), True) | info: Source - --> main.py:10:31 + --> main2.py:10:52 | 8 | def baz(a: int, b: str, c: bool): pass 9 | - 10 | baz(foo(5), bar(bar('test')), True) - | ^ + 10 | baz([a=]foo([x=]5), [b=]bar([y=]bar([y=]'test')), [c=]True) + | ^ | "); } #[test] fn test_method_chaining() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " class A: def foo(self, value: int) -> 'A': @@ -2377,12 +4059,12 @@ mod tests { 5 | def bar(self, name: str) -> 'A': | info: Source - --> main.py:8:9 + --> main2.py:8:10 | 6 | return self 7 | def baz(self): pass - 8 | A().foo(42).bar('test').baz() - | ^ + 8 | A().foo([value=]42).bar([name=]'test').baz() + | ^^^^^ | info[inlay-hint-location]: Inlay Hint Target @@ -2396,19 +4078,19 @@ mod tests { 7 | def baz(self): pass | info: Source - --> main.py:8:17 + --> main2.py:8:26 | 6 | return self 7 | def baz(self): pass - 8 | A().foo(42).bar('test').baz() - | ^ + 8 | A().foo([value=]42).bar([name=]'test').baz() + | ^^^^ | "); } #[test] fn test_nexted_keyword_function_calls() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: str) -> str: return x @@ -2433,19 +4115,19 @@ mod tests { 4 | def bar(y: int): pass | info: Source - --> main.py:5:11 + --> main2.py:5:12 | 3 | return x 4 | def bar(y: int): pass - 5 | bar(y=foo('test')) - | ^ + 5 | bar(y=foo([x=]'test')) + | ^ | "); } #[test] fn test_lambda_function_calls() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " foo = lambda x: x * 2 bar = lambda a, b: a + b @@ -2463,7 +4145,7 @@ mod tests { #[test] fn test_complex_parameter_combinations() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass foo(1, 'pos', 3.14, False, e=42) @@ -2484,12 +4166,12 @@ mod tests { 4 | foo(1, 'pos', 3.14, e=42, f='custom') | info: Source - --> main.py:3:15 + --> main2.py:3:16 | 2 | def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass - 3 | foo(1, 'pos', 3.14, False, e=42) - | ^ - 4 | foo(1, 'pos', 3.14, e=42, f='custom') + 3 | foo(1, 'pos', [c=]3.14, [d=]False, e=42) + | ^ + 4 | foo(1, 'pos', [c=]3.14, e=42, f='custom') | info[inlay-hint-location]: Inlay Hint Target @@ -2501,12 +4183,12 @@ mod tests { 4 | foo(1, 'pos', 3.14, e=42, f='custom') | info: Source - --> main.py:3:21 + --> main2.py:3:26 | 2 | def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass - 3 | foo(1, 'pos', 3.14, False, e=42) - | ^ - 4 | foo(1, 'pos', 3.14, e=42, f='custom') + 3 | foo(1, 'pos', [c=]3.14, [d=]False, e=42) + | ^ + 4 | foo(1, 'pos', [c=]3.14, e=42, f='custom') | info[inlay-hint-location]: Inlay Hint Target @@ -2518,12 +4200,12 @@ mod tests { 4 | foo(1, 'pos', 3.14, e=42, f='custom') | info: Source - --> main.py:4:15 + --> main2.py:4:16 | 2 | def foo(a: int, b: str, /, c: float, d: bool = True, *, e: int, f: str = 'default'): pass - 3 | foo(1, 'pos', 3.14, False, e=42) - 4 | foo(1, 'pos', 3.14, e=42, f='custom') - | ^ + 3 | foo(1, 'pos', [c=]3.14, [d=]False, e=42) + 4 | foo(1, 'pos', [c=]3.14, e=42, f='custom') + | ^ | "); } @@ -2557,19 +4239,19 @@ mod tests { 3 | pass | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | from foo import bar 3 | - 4 | bar(1) - | ^ + 4 | bar([x=]1) + | ^ | "); } #[test] fn test_overloaded_function_calls() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " from typing import overload @@ -2607,13 +4289,13 @@ mod tests { 7 | def foo(x: str) -> int: ... | info: Source - --> main.py:11:5 + --> main2.py:11:6 | 9 | return x 10 | - 11 | foo(42) - | ^ - 12 | foo('hello') + 11 | foo([x=]42) + | ^ + 12 | foo([x=]'hello') | info[inlay-hint-location]: Inlay Hint Target @@ -2626,18 +4308,18 @@ mod tests { 7 | def foo(x: str) -> int: ... | info: Source - --> main.py:12:5 + --> main2.py:12:6 | - 11 | foo(42) - 12 | foo('hello') - | ^ + 11 | foo([x=]42) + 12 | foo([x=]'hello') + | ^ | "); } #[test] fn test_disabled_function_argument_names() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass foo(1)", @@ -2654,7 +4336,7 @@ mod tests { #[test] fn test_function_call_out_of_range() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(x: int): pass def bar(y: int): pass @@ -2677,12 +4359,12 @@ mod tests { 4 | foo(1) | info: Source - --> main.py:4:5 + --> main2.py:4:6 | 2 | def foo(x: int): pass 3 | def bar(y: int): pass - 4 | foo(1) - | ^ + 4 | foo([x=]1) + | ^ 5 | bar(2) | "); @@ -2690,7 +4372,7 @@ mod tests { #[test] fn test_function_call_with_argument_name_starting_with_underscore() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo(_x: int, y: int): pass foo(1, 2)", @@ -2708,18 +4390,18 @@ mod tests { 3 | foo(1, 2) | info: Source - --> main.py:3:8 + --> main2.py:3:9 | 2 | def foo(_x: int, y: int): pass - 3 | foo(1, 2) - | ^ + 3 | foo(1, [y=]2) + | ^ | "); } #[test] fn test_function_call_different_formatting() { - let test = inlay_hint_test( + let mut test = inlay_hint_test( " def foo( x: int, @@ -2747,12 +4429,12 @@ mod tests { 5 | ): ... | info: Source - --> main.py:7:5 + --> main2.py:7:6 | 5 | ): ... 6 | - 7 | foo(1, 2) - | ^ + 7 | foo([x=]1, [y=]2) + | ^ | info[inlay-hint-location]: Inlay Hint Target @@ -2765,12 +4447,12 @@ mod tests { 5 | ): ... | info: Source - --> main.py:7:8 + --> main2.py:7:13 | 5 | ): ... 6 | - 7 | foo(1, 2) - | ^ + 7 | foo([x=]1, [y=]2) + | ^ | "); } diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 67a598ab61..13b116bdad 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -50,8 +50,8 @@ use crate::types::constraints::{ }; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; -pub use crate::types::display::DisplaySettings; use crate::types::display::TupleSpecialization; +pub use crate::types::display::{DisplaySettings, TypeDetail, TypeDisplayDetails}; use crate::types::enums::{enum_metadata, is_single_member_enum}; use crate::types::function::{ DataclassTransformerFlags, DataclassTransformerParams, FunctionDecorators, FunctionSpans, diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 3a2c6423dd..c3bb7fa510 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -11,7 +11,7 @@ use ruff_db::files::FilePath; use ruff_db::source::line_index; use ruff_python_ast::str::{Quote, TripleQuotes}; use ruff_python_literal::escape::AsciiEscape; -use ruff_text_size::{TextRange, TextSize}; +use ruff_text_size::{TextLen, TextRange, TextSize}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::Db; @@ -101,6 +101,210 @@ impl<'db> DisplaySettings<'db> { } } +/// Details about a type's formatting +/// +/// The `targets` and `details` are 1:1 (you can `zip` them) +pub struct TypeDisplayDetails<'db> { + /// The fully formatted type + pub label: String, + /// Ranges in the label + pub targets: Vec, + /// Metadata for each range + pub details: Vec>, +} + +/// Abstraction over "are we doing normal formatting, or tracking ranges with metadata?" +enum TypeWriter<'a, 'b, 'db> { + Formatter(&'a mut Formatter<'b>), + Details(TypeDetailsWriter<'db>), +} +/// Writer that builds a string with range tracking +struct TypeDetailsWriter<'db> { + label: String, + targets: Vec, + details: Vec>, +} + +impl<'db> TypeDetailsWriter<'db> { + fn new() -> Self { + Self { + label: String::new(), + targets: Vec::new(), + details: Vec::new(), + } + } + + /// Produce type info + fn finish_type_details(self) -> TypeDisplayDetails<'db> { + TypeDisplayDetails { + label: self.label, + targets: self.targets, + details: self.details, + } + } + + /// Produce function signature info + fn finish_signature_details(self) -> SignatureDisplayDetails { + // We use SignatureStart and SignatureEnd to delimit nested function signatures inside + // this function signature. We only care about the parameters of the outermost function + // which should introduce it's own SignatureStart and SignatureEnd + let mut parameter_ranges = Vec::new(); + let mut parameter_names = Vec::new(); + let mut parameter_nesting = 0; + for (target, detail) in self.targets.into_iter().zip(self.details) { + match detail { + TypeDetail::SignatureStart => parameter_nesting += 1, + TypeDetail::SignatureEnd => parameter_nesting -= 1, + TypeDetail::Parameter(parameter) => { + if parameter_nesting <= 1 { + // We found parameters at the top-level, record them + parameter_names.push(parameter); + parameter_ranges.push(target); + } + } + TypeDetail::Type(_) => { /* don't care */ } + } + } + + SignatureDisplayDetails { + label: self.label, + parameter_names, + parameter_ranges, + } + } +} + +impl<'a, 'b, 'db> TypeWriter<'a, 'b, 'db> { + /// Indicate the given detail is about to start being written to this Writer + /// + /// This creates a scoped guard that when Dropped will record the given detail + /// as spanning from when it was introduced to when it was dropped. + fn with_detail<'c>(&'c mut self, detail: TypeDetail<'db>) -> TypeDetailGuard<'a, 'b, 'c, 'db> { + let start = match self { + TypeWriter::Formatter(_) => None, + TypeWriter::Details(details) => Some(details.label.text_len()), + }; + TypeDetailGuard { + start, + inner: self, + payload: Some(detail), + } + } + + fn join<'c>(&'c mut self, separator: &'static str) -> Join<'a, 'b, 'c, 'db> { + Join { + fmt: self, + separator, + result: Ok(()), + seen_first: false, + } + } +} + +impl std::fmt::Write for TypeWriter<'_, '_, '_> { + fn write_str(&mut self, val: &str) -> fmt::Result { + match self { + TypeWriter::Formatter(formatter) => formatter.write_str(val), + TypeWriter::Details(formatter) => formatter.write_str(val), + } + } +} +impl std::fmt::Write for TypeDetailsWriter<'_> { + fn write_str(&mut self, val: &str) -> fmt::Result { + self.label.write_str(val) + } +} + +trait FmtDetailed<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result; +} + +struct Join<'a, 'b, 'c, 'db> { + fmt: &'c mut TypeWriter<'a, 'b, 'db>, + separator: &'static str, + result: fmt::Result, + seen_first: bool, +} + +impl<'db> Join<'_, '_, '_, 'db> { + fn entry(&mut self, item: &dyn FmtDetailed<'db>) -> &mut Self { + if self.seen_first { + self.result = self + .result + .and_then(|()| self.fmt.write_str(self.separator)); + } else { + self.seen_first = true; + } + self.result = self.result.and_then(|()| item.fmt_detailed(self.fmt)); + self + } + + fn entries(&mut self, items: I) -> &mut Self + where + I: IntoIterator, + F: FmtDetailed<'db>, + { + for item in items { + self.entry(&item); + } + self + } + + fn finish(&mut self) -> fmt::Result { + self.result + } +} + +pub enum TypeDetail<'db> { + /// Dummy item to indicate a function signature's parameters have started + SignatureStart, + /// Dummy item to indicate a function signature's parameters have ended + SignatureEnd, + /// A function signature's parameter + Parameter(String), + /// A type + Type(Type<'db>), +} + +/// Look on my Works, ye Mighty, and despair! +/// +/// It's quite important that we avoid conflating any of these lifetimes, or else the +/// borrowchecker will throw a ton of confusing errors about things not living long +/// enough. If you get those kinds of errors, it's probably because you introduced +/// something like `&'db self`, which, while convenient, and sometimes works, is imprecise. +struct TypeDetailGuard<'a, 'b, 'c, 'db> { + inner: &'c mut TypeWriter<'a, 'b, 'db>, + start: Option, + payload: Option>, +} + +impl Drop for TypeDetailGuard<'_, '_, '_, '_> { + fn drop(&mut self) { + // The fallibility here is primarily retrieving `TypeWriter::Details` + // everything else is ideally-never-fails pedantry (yay for pedantry!) + if let TypeWriter::Details(details) = &mut self.inner + && let Some(start) = self.start + && let Some(payload) = self.payload.take() + { + let target = TextRange::new(start, details.label.text_len()); + details.targets.push(target); + details.details.push(payload); + } + } +} + +impl<'a, 'b, 'db> std::ops::Deref for TypeDetailGuard<'a, 'b, '_, 'db> { + type Target = TypeWriter<'a, 'b, 'db>; + fn deref(&self) -> &Self::Target { + self.inner + } +} +impl std::ops::DerefMut for TypeDetailGuard<'_, '_, '_, '_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum QualificationLevel { ModuleName, @@ -245,8 +449,20 @@ pub struct DisplayType<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> DisplayType<'db> { + pub fn to_string_parts(&self) -> TypeDisplayDetails<'db> { + let mut f = TypeWriter::Details(TypeDetailsWriter::new()); + self.fmt_detailed(&mut f).unwrap(); + + match f { + TypeWriter::Details(details) => details.finish_type_details(), + TypeWriter::Formatter(_) => unreachable!("Expected Details variant"), + } + } +} + +impl<'db> FmtDetailed<'db> for DisplayType<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let representation = self.ty.representation(self.db, self.settings.clone()); match self.ty { Type::IntLiteral(_) @@ -254,13 +470,21 @@ impl Display for DisplayType<'_> { | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::EnumLiteral(_) => { - write!(f, "Literal[{representation}]") + f.write_str("Literal[")?; + representation.fmt_detailed(f)?; + f.write_str("]") } - _ => representation.fmt(f), + _ => representation.fmt_detailed(f), } } } +impl Display for DisplayType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl fmt::Debug for DisplayType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(self, f) @@ -329,8 +553,8 @@ struct ClassDisplay<'db> { settings: DisplaySettings<'db>, } -impl Display for ClassDisplay<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for ClassDisplay<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let qualification_level = self.settings.qualified.get(&**self.class.name(self.db)); if qualification_level.is_some() { for parent in self.class.qualified_name_components(self.db) { @@ -338,7 +562,8 @@ impl Display for ClassDisplay<'_> { f.write_char('.')?; } } - f.write_str(self.class.name(self.db))?; + f.with_detail(TypeDetail::Type(Type::ClassLiteral(self.class))) + .write_str(self.class.name(self.db))?; if qualification_level == Some(&QualificationLevel::FileAndLineNumber) { let file = self.class.file(self.db); let path = file.path(self.db); @@ -359,6 +584,12 @@ impl Display for ClassDisplay<'_> { } } +impl Display for ClassDisplay<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + /// Writes the string representation of a type, which is the value displayed either as /// `Literal[]` or `Literal[, ]` for literal types or as `` for /// non literals @@ -370,35 +601,41 @@ struct DisplayRepresentation<'db> { impl Display for DisplayRepresentation<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + +impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { match self.ty { - Type::Dynamic(dynamic) => dynamic.fmt(f), - Type::Never => f.write_str("Never"), + Type::Dynamic(dynamic) => write!(f, "{dynamic}"), + Type::Never => f.with_detail(TypeDetail::Type(self.ty)).write_str("Never"), Type::NominalInstance(instance) => { let class = instance.class(self.db); match (class, class.known(self.db)) { - (_, Some(KnownClass::NoneType)) => f.write_str("None"), - (_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"), + (_, Some(KnownClass::NoneType)) => f.with_detail(TypeDetail::Type(self.ty)).write_str("None"), + (_, Some(KnownClass::NoDefaultType)) => f.with_detail(TypeDetail::Type(self.ty)).write_str("NoDefault"), (ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias .specialization(self.db) .tuple(self.db) .expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`") .display_with(self.db, self.settings.clone()) - .fmt(f), + .fmt_detailed(f), (ClassType::NonGeneric(class), _) => { - class.display_with(self.db, self.settings.clone()).fmt(f) + class.display_with(self.db, self.settings.clone()).fmt_detailed(f) }, - (ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings.clone()).fmt(f), + (ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings.clone()).fmt_detailed(f), } } Type::ProtocolInstance(protocol) => match protocol.inner { Protocol::FromClass(class) => match *class { - ClassType::NonGeneric(class) => { - class.display_with(self.db, self.settings.clone()).fmt(f) - } - ClassType::Generic(alias) => { - alias.display_with(self.db, self.settings.clone()).fmt(f) - } + ClassType::NonGeneric(class) => class + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), + ClassType::Generic(alias) => alias + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), }, Protocol::Synthesized(synthetic) => { f.write_str(" { }, Type::PropertyInstance(_) => f.write_str("property"), Type::ModuleLiteral(module) => { - write!(f, "", module.module(self.db).name(self.db)) + write!( + f.with_detail(TypeDetail::Type(self.ty)), + "", + module.module(self.db).name(self.db) + ) } Type::ClassLiteral(class) => write!( - f, + f.with_detail(TypeDetail::Type(self.ty)), "", class.display_with(self.db, self.settings.clone()) ), Type::GenericAlias(generic) => write!( - f, + f.with_detail(TypeDetail::Type(self.ty)), "", generic.display_with(self.db, self.settings.singleline()) ), Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() { SubclassOfInner::Class(ClassType::NonGeneric(class)) => { - write!( - f, - "type[{}]", - class.display_with(self.db, self.settings.clone()) - ) + f.write_str("type[")?; + class + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; + f.write_str("]") } SubclassOfInner::Class(ClassType::Generic(alias)) => { - write!( - f, - "type[{}]", - alias.display_with(self.db, self.settings.singleline()) - ) + f.write_str("type[")?; + alias + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; + f.write_str("]") } SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"), }, - Type::SpecialForm(special_form) => special_form.fmt(f), - Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f), - Type::FunctionLiteral(function) => { - function.display_with(self.db, self.settings.clone()).fmt(f) - } - Type::Callable(callable) => { - callable.display_with(self.db, self.settings.clone()).fmt(f) - } + Type::SpecialForm(special_form) => write!(f, "{special_form}"), + Type::KnownInstance(known_instance) => write!(f, "{}", known_instance.repr(self.db)), + Type::FunctionLiteral(function) => function + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), + Type::Callable(callable) => callable + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), Type::BoundMethod(bound_method) => { let function = bound_method.function(self.db); let self_ty = bound_method.self_instance(self.db); @@ -467,16 +708,18 @@ impl Display for DisplayRepresentation<'_> { settings: self.settings.clone(), }; - write!( - f, - "bound method {instance}.{method}{type_parameters}{signature}", - method = function.name(self.db), - instance = self_ty.display_with(self.db, self.settings.singleline()), - type_parameters = type_parameters, - signature = signature - .bind_self(self.db, Some(typing_self_ty)) - .display_with(self.db, self.settings.clone()) - ) + f.write_str("bound method ")?; + self_ty + .display_with(self.db, self.settings.singleline()) + .fmt_detailed(f)?; + f.write_char('.')?; + f.with_detail(TypeDetail::Type(self.ty)) + .write_str(function.name(self.db))?; + type_parameters.fmt_detailed(f)?; + signature + .bind_self(self.db, Some(typing_self_ty)) + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f) } signatures => { // TODO: How to display overloads? @@ -492,6 +735,7 @@ impl Display for DisplayRepresentation<'_> { .display_with(self.db, self.settings.clone()), ); } + join.finish()?; if !self.settings.multiline { f.write_str("]")?; } @@ -560,14 +804,16 @@ impl Display for DisplayRepresentation<'_> { Type::DataclassTransformer(_) => { f.write_str("") } - Type::Union(union) => union.display_with(self.db, self.settings.clone()).fmt(f), + Type::Union(union) => union + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), Type::Intersection(intersection) => intersection .display_with(self.db, self.settings.clone()) - .fmt(f), - Type::IntLiteral(n) => n.fmt(f), + .fmt_detailed(f), + Type::IntLiteral(n) => write!(f, "{n}"), Type::BooleanLiteral(boolean) => f.write_str(if boolean { "True" } else { "False" }), Type::StringLiteral(string) => { - string.display_with(self.db, self.settings.clone()).fmt(f) + write!(f, "{}", string.display_with(self.db, self.settings.clone())) } Type::LiteralString => f.write_str("LiteralString"), Type::BytesLiteral(bytes) => { @@ -575,33 +821,35 @@ impl Display for DisplayRepresentation<'_> { escape.bytes_repr(TripleQuotes::No).write(f) } - Type::EnumLiteral(enum_literal) => write!( - f, - "{enum_class}.{literal_name}", - enum_class = enum_literal + Type::EnumLiteral(enum_literal) => { + enum_literal .enum_class(self.db) - .display_with(self.db, self.settings.clone()), - literal_name = enum_literal.name(self.db) - ), - Type::TypeVar(bound_typevar) => bound_typevar.identity(self.db).display(self.db).fmt(f), + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; + write!(f, ".{}", enum_literal.name(self.db)) + } + Type::TypeVar(bound_typevar) => { + write!(f, "{}", bound_typevar.identity(self.db).display(self.db)) + } Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), Type::BoundSuper(bound_super) => { - write!( - f, - "", - pivot = Type::from(bound_super.pivot_class(self.db)) - .display_with(self.db, self.settings.singleline()), - owner = Type::from(bound_super.owner(self.db)) - .display_with(self.db, self.settings.singleline()) - ) + f.write_str("") } Type::TypeIs(type_is) => { f.write_str("TypeIs[")?; type_is .return_type(self.db) .display_with(self.db, self.settings.singleline()) - .fmt(f)?; + .fmt_detailed(f)?; if let Some(name) = type_is.place_name(self.db) { f.write_str(" @ ")?; f.write_str(&name)?; @@ -613,17 +861,19 @@ impl Display for DisplayRepresentation<'_> { .class_literal(self.db) .0 .display_with(self.db, self.settings.clone()) - .fmt(f), + .fmt_detailed(f), Type::TypeAlias(alias) => { f.write_str(alias.name(self.db))?; match alias.specialization(self.db) { None => Ok(()), Some(specialization) => specialization .display_short(self.db, TupleSpecialization::No, self.settings.clone()) - .fmt(f), + .fmt_detailed(f), } } - Type::NewTypeInstance(newtype) => f.write_str(newtype.name(self.db)), + Type::NewTypeInstance(newtype) => f + .with_detail(TypeDetail::Type(self.ty)) + .write_str(newtype.name(self.db)), } } } @@ -653,11 +903,11 @@ impl Display for DisplayBoundTypeVarIdentity<'_> { } impl<'db> TupleSpec<'db> { - pub(crate) fn display_with( - &'db self, + pub(crate) fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayTuple<'db> { + ) -> DisplayTuple<'a, 'db> { DisplayTuple { tuple: self, db, @@ -666,14 +916,14 @@ impl<'db> TupleSpec<'db> { } } -pub(crate) struct DisplayTuple<'db> { - tuple: &'db TupleSpec<'db>, +pub(crate) struct DisplayTuple<'a, 'db> { + tuple: &'a TupleSpec<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl Display for DisplayTuple<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayTuple<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { f.write_str("tuple[")?; match self.tuple { TupleSpec::Fixed(tuple) => { @@ -683,7 +933,7 @@ impl Display for DisplayTuple<'_> { } else { elements .display_with(self.db, self.settings.singleline()) - .fmt(f)?; + .fmt_detailed(f)?; } } @@ -706,7 +956,7 @@ impl Display for DisplayTuple<'_> { tuple .prefix .display_with(self.db, self.settings.singleline()) - .fmt(f)?; + .fmt_detailed(f)?; f.write_str(", ")?; } if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() { @@ -715,7 +965,7 @@ impl Display for DisplayTuple<'_> { tuple .variable .display_with(self.db, self.settings.singleline()) - .fmt(f)?; + .fmt_detailed(f)?; f.write_str(", ...")?; if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() { f.write_str("]")?; @@ -725,7 +975,7 @@ impl Display for DisplayTuple<'_> { tuple .suffix .display_with(self.db, self.settings.singleline()) - .fmt(f)?; + .fmt_detailed(f)?; } } } @@ -733,6 +983,12 @@ impl Display for DisplayTuple<'_> { } } +impl Display for DisplayTuple<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> OverloadLiteral<'db> { // Not currently used, but useful for debugging. #[expect(dead_code)] @@ -759,8 +1015,8 @@ pub(crate) struct DisplayOverloadLiteral<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayOverloadLiteral<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayOverloadLiteral<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let signature = self.literal.signature(self.db); let type_parameters = DisplayOptionalGenericContext { generic_context: signature.generic_context.as_ref(), @@ -768,13 +1024,18 @@ impl Display for DisplayOverloadLiteral<'_> { settings: self.settings.clone(), }; - write!( - f, - "def {name}{type_parameters}{signature}", - name = self.literal.name(self.db), - type_parameters = type_parameters, - signature = signature.display_with(self.db, self.settings.clone()) - ) + f.write_str("def ")?; + write!(f, "{}", self.literal.name(self.db))?; + type_parameters.fmt_detailed(f)?; + signature + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f) + } +} + +impl Display for DisplayOverloadLiteral<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) } } @@ -798,8 +1059,8 @@ pub(crate) struct DisplayFunctionType<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayFunctionType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayFunctionType<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let signature = self.ty.signature(self.db); match signature.overloads.as_slice() { @@ -810,13 +1071,12 @@ impl Display for DisplayFunctionType<'_> { settings: self.settings.clone(), }; - write!( - f, - "def {name}{type_parameters}{signature}", - name = self.ty.name(self.db), - type_parameters = type_parameters, - signature = signature.display_with(self.db, self.settings.clone()) - ) + f.write_str("def ")?; + write!(f, "{}", self.ty.name(self.db))?; + type_parameters.fmt_detailed(f)?; + signature + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f) } signatures => { // TODO: How to display overloads? @@ -828,6 +1088,7 @@ impl Display for DisplayFunctionType<'_> { for signature in signatures { join.entry(&signature.display_with(self.db, self.settings.clone())); } + join.finish()?; if !self.settings.multiline { f.write_str("]")?; } @@ -837,13 +1098,19 @@ impl Display for DisplayFunctionType<'_> { } } +impl Display for DisplayFunctionType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> GenericAlias<'db> { - pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { + pub(crate) fn display(self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { self.display_with(db, DisplaySettings::default()) } pub(crate) fn display_with( - &'db self, + self, db: &'db dyn Db, settings: DisplaySettings<'db>, ) -> DisplayGenericAlias<'db> { @@ -863,10 +1130,12 @@ pub(crate) struct DisplayGenericAlias<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayGenericAlias<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayGenericAlias<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if let Some(tuple) = self.specialization.tuple(self.db) { - tuple.display_with(self.db, self.settings.clone()).fmt(f) + tuple + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f) } else { let prefix = match self.specialization.materialization_kind(self.db) { None => "", @@ -877,28 +1146,34 @@ impl Display for DisplayGenericAlias<'_> { None => "", Some(_) => "]", }; - write!( - f, - "{prefix}{origin}{specialization}{suffix}", - prefix = prefix, - origin = self.origin.display_with(self.db, self.settings.clone()), - specialization = self.specialization.display_short( + f.write_str(prefix)?; + self.origin + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; + self.specialization + .display_short( self.db, TupleSpecialization::from_class(self.db, self.origin), - self.settings.clone() - ), - suffix = suffix, - ) + self.settings.clone(), + ) + .fmt_detailed(f)?; + f.write_str(suffix) } } } +impl Display for DisplayGenericAlias<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> GenericContext<'db> { - pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> { + pub fn display<'a>(&'a self, db: &'db dyn Db) -> DisplayGenericContext<'a, 'db> { Self::display_with(self, db, DisplaySettings::default()) } - pub fn display_full(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> { + pub fn display_full<'a>(&'a self, db: &'db dyn Db) -> DisplayGenericContext<'a, 'db> { DisplayGenericContext { generic_context: self, db, @@ -907,11 +1182,11 @@ impl<'db> GenericContext<'db> { } } - pub fn display_with( - &'db self, + pub fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayGenericContext<'db> { + ) -> DisplayGenericContext<'a, 'db> { DisplayGenericContext { generic_context: self, db, @@ -921,34 +1196,40 @@ impl<'db> GenericContext<'db> { } } -struct DisplayOptionalGenericContext<'db> { - generic_context: Option<&'db GenericContext<'db>>, +struct DisplayOptionalGenericContext<'a, 'db> { + generic_context: Option<&'a GenericContext<'db>>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl Display for DisplayOptionalGenericContext<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayOptionalGenericContext<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if let Some(generic_context) = self.generic_context { generic_context .display_with(self.db, self.settings.clone()) - .fmt(f) + .fmt_detailed(f) } else { Ok(()) } } } -pub struct DisplayGenericContext<'db> { - generic_context: &'db GenericContext<'db>, +impl Display for DisplayOptionalGenericContext<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + +pub struct DisplayGenericContext<'a, 'db> { + generic_context: &'a GenericContext<'db>, db: &'db dyn Db, #[expect(dead_code)] settings: DisplaySettings<'db>, full: bool, } -impl DisplayGenericContext<'_> { - fn fmt_normal(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> DisplayGenericContext<'_, 'db> { + fn fmt_normal(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let variables = self.generic_context.variables(self.db); let non_implicit_variables: Vec<_> = variables @@ -969,21 +1250,21 @@ impl DisplayGenericContext<'_> { f.write_char(']') } - fn fmt_full(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt_full(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let variables = self.generic_context.variables(self.db); f.write_char('[')?; for (idx, bound_typevar) in variables.enumerate() { if idx > 0 { f.write_str(", ")?; } - bound_typevar.identity(self.db).display(self.db).fmt(f)?; + write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?; } f.write_char(']') } } -impl Display for DisplayGenericContext<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayGenericContext<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if self.full { self.fmt_full(f) } else { @@ -992,6 +1273,12 @@ impl Display for DisplayGenericContext<'_> { } } +impl Display for DisplayGenericContext<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> Specialization<'db> { pub fn display(self, db: &'db dyn Db) -> DisplaySpecialization<'db> { self.display_short(db, TupleSpecialization::No, DisplaySettings::default()) @@ -1032,15 +1319,16 @@ pub struct DisplaySpecialization<'db> { full: bool, } -impl DisplaySpecialization<'_> { - fn fmt_normal(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> DisplaySpecialization<'db> { + fn fmt_normal(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { f.write_char('[')?; let types = self.specialization.types(self.db); for (idx, ty) in types.iter().enumerate() { if idx > 0 { f.write_str(", ")?; } - ty.display_with(self.db, self.settings.clone()).fmt(f)?; + ty.display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } if self.tuple_specialization.is_yes() { f.write_str(", ...")?; @@ -1048,7 +1336,7 @@ impl DisplaySpecialization<'_> { f.write_char(']') } - fn fmt_full(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt_full(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { f.write_char('[')?; let variables = self .specialization @@ -1059,19 +1347,17 @@ impl DisplaySpecialization<'_> { if idx > 0 { f.write_str(", ")?; } - write!( - f, - "{} = {}", - bound_typevar.identity(self.db).display(self.db), - ty.display_with(self.db, self.settings.clone()), - )?; + write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?; + f.write_str(" = ")?; + ty.display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } f.write_char(']') } } -impl Display for DisplaySpecialization<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplaySpecialization<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if self.full { self.fmt_full(f) } else { @@ -1080,6 +1366,12 @@ impl Display for DisplaySpecialization<'_> { } } +impl Display for DisplaySpecialization<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TupleSpecialization { Yes, @@ -1101,15 +1393,15 @@ impl TupleSpecialization { } impl<'db> CallableType<'db> { - pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayCallableType<'db> { + pub(crate) fn display<'a>(&'a self, db: &'db dyn Db) -> DisplayCallableType<'a, 'db> { Self::display_with(self, db, DisplaySettings::default()) } - pub(crate) fn display_with( - &'db self, + pub(crate) fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayCallableType<'db> { + ) -> DisplayCallableType<'a, 'db> { DisplayCallableType { signatures: self.signatures(db), db, @@ -1118,18 +1410,18 @@ impl<'db> CallableType<'db> { } } -pub(crate) struct DisplayCallableType<'db> { - signatures: &'db CallableSignature<'db>, +pub(crate) struct DisplayCallableType<'a, 'db> { + signatures: &'a CallableSignature<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl Display for DisplayCallableType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayCallableType<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { match self.signatures.overloads.as_slice() { [signature] => signature .display_with(self.db, self.settings.clone()) - .fmt(f), + .fmt_detailed(f), signatures => { // TODO: How to display overloads? if !self.settings.multiline { @@ -1150,16 +1442,22 @@ impl Display for DisplayCallableType<'_> { } } +impl Display for DisplayCallableType<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> Signature<'db> { - pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplaySignature<'db> { + pub(crate) fn display<'a>(&'a self, db: &'db dyn Db) -> DisplaySignature<'a, 'db> { Self::display_with(self, db, DisplaySettings::default()) } - pub(crate) fn display_with( - &'db self, + pub(crate) fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplaySignature<'db> { + ) -> DisplaySignature<'a, 'db> { DisplaySignature { parameters: self.parameters(), return_ty: self.return_ty, @@ -1169,37 +1467,42 @@ impl<'db> Signature<'db> { } } -pub(crate) struct DisplaySignature<'db> { - parameters: &'db Parameters<'db>, +pub(crate) struct DisplaySignature<'a, 'db> { + parameters: &'a Parameters<'db>, return_ty: Option>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl DisplaySignature<'_> { +impl DisplaySignature<'_, '_> { /// Get detailed display information including component ranges pub(crate) fn to_string_parts(&self) -> SignatureDisplayDetails { - let mut writer = SignatureWriter::Details(SignatureDetailsWriter::new()); - self.write_signature(&mut writer).unwrap(); + let mut f = TypeWriter::Details(TypeDetailsWriter::new()); + self.fmt_detailed(&mut f).unwrap(); - match writer { - SignatureWriter::Details(details) => details.finish(), - SignatureWriter::Formatter(_) => unreachable!("Expected Details variant"), + match f { + TypeWriter::Details(details) => details.finish_signature_details(), + TypeWriter::Formatter(_) => unreachable!("Expected Details variant"), } } +} - /// Internal method to write signature with the signature writer - fn write_signature(&self, writer: &mut SignatureWriter) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { + // Immediately write a marker signaling we're starting a signature + let _ = f.with_detail(TypeDetail::SignatureStart); + // When we exit this function, write a marker signaling we're ending a signature + let mut f = f.with_detail(TypeDetail::SignatureEnd); let multiline = self.settings.multiline && self.parameters.len() > 1; // Opening parenthesis - writer.write_char('(')?; + f.write_char('(')?; if multiline { - writer.write_str("\n ")?; + f.write_str("\n ")?; } if self.parameters.is_gradual() { // We represent gradual form as `...` in the signature, internally the parameters still // contain `(*args, **kwargs)` parameters. - writer.write_str("...")?; + f.write_str("...")?; } else { let mut star_added = false; let mut needs_slash = false; @@ -1210,9 +1513,9 @@ impl DisplaySignature<'_> { // Handle special separators if !star_added && parameter.is_keyword_only() { if !first { - writer.write_str(arg_separator)?; + f.write_str(arg_separator)?; } - writer.write_char('*')?; + f.write_char('*')?; star_added = true; first = false; } @@ -1220,145 +1523,56 @@ impl DisplaySignature<'_> { needs_slash = true; } else if needs_slash { if !first { - writer.write_str(arg_separator)?; + f.write_str(arg_separator)?; } - writer.write_char('/')?; + f.write_char('/')?; needs_slash = false; first = false; } // Add comma before parameter if not first if !first { - writer.write_str(arg_separator)?; + f.write_str(arg_separator)?; } // Write parameter with range tracking - let param_name = parameter.display_name(); - writer.write_parameter( - ¶meter.display_with(self.db, self.settings.singleline()), - param_name.as_deref(), - )?; + let param_name = parameter + .display_name() + .map(|name| name.to_string()) + .unwrap_or_default(); + parameter + .display_with(self.db, self.settings.singleline()) + .fmt_detailed(&mut f.with_detail(TypeDetail::Parameter(param_name)))?; first = false; } if needs_slash { if !first { - writer.write_str(arg_separator)?; + f.write_str(arg_separator)?; } - writer.write_char('/')?; + f.write_char('/')?; } } if multiline { - writer.write_char('\n')?; + f.write_char('\n')?; } // Closing parenthesis - writer.write_char(')')?; + f.write_char(')')?; // Return type let return_ty = self.return_ty.unwrap_or_else(Type::unknown); - writer.write_return_type(&return_ty.display_with(self.db, self.settings.singleline()))?; - - Ok(()) + f.write_str(" -> ")?; + return_ty + .display_with(self.db, self.settings.singleline()) + .fmt_detailed(&mut f) } } -impl Display for DisplaySignature<'_> { +impl Display for DisplaySignature<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut writer = SignatureWriter::Formatter(f); - self.write_signature(&mut writer) - } -} - -/// Writer for building signature strings with different output targets -enum SignatureWriter<'a, 'b> { - /// Write directly to a formatter (for Display trait) - Formatter(&'a mut Formatter<'b>), - /// Build a string with range tracking (for `to_string_parts`) - Details(SignatureDetailsWriter), -} - -/// Writer that builds a string with range tracking -struct SignatureDetailsWriter { - label: String, - parameter_ranges: Vec, - parameter_names: Vec, -} - -impl SignatureDetailsWriter { - fn new() -> Self { - Self { - label: String::new(), - parameter_ranges: Vec::new(), - parameter_names: Vec::new(), - } - } - - fn finish(self) -> SignatureDisplayDetails { - SignatureDisplayDetails { - label: self.label, - parameter_ranges: self.parameter_ranges, - parameter_names: self.parameter_names, - } - } -} - -impl SignatureWriter<'_, '_> { - fn write_char(&mut self, c: char) -> fmt::Result { - match self { - SignatureWriter::Formatter(f) => f.write_char(c), - SignatureWriter::Details(details) => { - details.label.push(c); - Ok(()) - } - } - } - - fn write_str(&mut self, s: &str) -> fmt::Result { - match self { - SignatureWriter::Formatter(f) => f.write_str(s), - SignatureWriter::Details(details) => { - details.label.push_str(s); - Ok(()) - } - } - } - - fn write_parameter(&mut self, param: &T, param_name: Option<&str>) -> fmt::Result { - match self { - SignatureWriter::Formatter(f) => param.fmt(f), - SignatureWriter::Details(details) => { - let param_start = details.label.len(); - let param_display = param.to_string(); - details.label.push_str(¶m_display); - - // Use TextSize::try_from for safe conversion, falling back to empty range on overflow - let start = TextSize::try_from(param_start).unwrap_or_default(); - let length = TextSize::try_from(param_display.len()).unwrap_or_default(); - details.parameter_ranges.push(TextRange::at(start, length)); - - // Store the parameter name if available - if let Some(name) = param_name { - details.parameter_names.push(name.to_string()); - } else { - details.parameter_names.push(String::new()); - } - - Ok(()) - } - } - } - - fn write_return_type(&mut self, return_ty: &T) -> fmt::Result { - match self { - SignatureWriter::Formatter(f) => write!(f, " -> {return_ty}"), - SignatureWriter::Details(details) => { - let return_display = format!(" -> {return_ty}"); - details.label.push_str(&return_display); - Ok(()) - } - } + self.fmt_detailed(&mut TypeWriter::Formatter(f)) } } @@ -1374,11 +1588,11 @@ pub(crate) struct SignatureDisplayDetails { } impl<'db> Parameter<'db> { - fn display_with( - &'db self, + fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayParameter<'db> { + ) -> DisplayParameter<'a, 'db> { DisplayParameter { param: self, db, @@ -1387,50 +1601,51 @@ impl<'db> Parameter<'db> { } } -struct DisplayParameter<'db> { - param: &'db Parameter<'db>, +struct DisplayParameter<'a, 'db> { + param: &'a Parameter<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl Display for DisplayParameter<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayParameter<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if let Some(name) = self.param.display_name() { f.write_str(&name)?; if let Some(annotated_type) = self.param.annotated_type() { if self.param.should_annotation_be_displayed() { - write!( - f, - ": {}", - annotated_type.display_with(self.db, self.settings.clone()) - )?; + f.write_str(": ")?; + annotated_type + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } } // Default value can only be specified if `name` is given. if let Some(default_ty) = self.param.default_type() { if self.param.annotated_type().is_some() { - write!( - f, - " = {}", - default_ty.display_with(self.db, self.settings.clone()) - )?; + f.write_str(" = ")?; } else { - write!( - f, - "={}", - default_ty.display_with(self.db, self.settings.clone()) - )?; + f.write_str("=")?; } + default_ty + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } } else if let Some(ty) = self.param.annotated_type() { // This case is specifically for the `Callable` signature where name and default value // cannot be provided. - ty.display_with(self.db, self.settings.clone()).fmt(f)?; + ty.display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; } Ok(()) } } +impl Display for DisplayParameter<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + #[derive(Debug, Copy, Clone)] struct TruncationPolicy { max: usize, @@ -1470,11 +1685,11 @@ impl Display for DisplayOmitted { } impl<'db> UnionType<'db> { - fn display_with( - &'db self, + fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayUnionType<'db> { + ) -> DisplayUnionType<'a, 'db> { DisplayUnionType { db, ty: self, @@ -1483,8 +1698,8 @@ impl<'db> UnionType<'db> { } } -struct DisplayUnionType<'db> { - ty: &'db UnionType<'db>, +struct DisplayUnionType<'a, 'db> { + ty: &'a UnionType<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } @@ -1494,8 +1709,8 @@ const UNION_POLICY: TruncationPolicy = TruncationPolicy { max_when_elided: 3, }; -impl Display for DisplayUnionType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayUnionType<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { fn is_condensable(ty: Type<'_>) -> bool { matches!( ty, @@ -1520,7 +1735,16 @@ impl Display for DisplayUnionType<'_> { assert_ne!(total_entries, 0); - let mut join = f.join(" | "); + // Done manually because we have a mix of FmtDetailed and Display + let mut is_first = true; + let mut write_join = |f: &mut TypeWriter<'_, '_, 'db>| { + if !is_first { + f.write_str(" | ") + } else { + is_first = false; + Ok(()) + } + }; let display_limit = UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions); @@ -1536,40 +1760,55 @@ impl Display for DisplayUnionType<'_> { if is_condensable(*element) { if let Some(condensed_types) = condensed_types.take() { displayed_entries += 1; - join.entry(&DisplayLiteralGroup { - literals: condensed_types, - db: self.db, - settings: self.settings.singleline(), - }); + write_join(f)?; + write!( + f, + "{}", + DisplayLiteralGroup { + literals: condensed_types, + db: self.db, + settings: self.settings.singleline(), + } + )?; } } else { displayed_entries += 1; - join.entry(&DisplayMaybeParenthesizedType { + write_join(f)?; + DisplayMaybeParenthesizedType { ty: *element, db: self.db, settings: self.settings.singleline(), - }); + } + .fmt_detailed(f)?; } } if !self.settings.preserve_full_unions { let omitted_entries = total_entries.saturating_sub(displayed_entries); if omitted_entries > 0 { - join.entry(&DisplayOmitted { - count: omitted_entries, - singular: "union element", - plural: "union elements", - }); + write_join(f)?; + write!( + f, + "{}", + DisplayOmitted { + count: omitted_entries, + singular: "union element", + plural: "union elements", + } + )?; } } - - join.finish()?; - Ok(()) } } -impl fmt::Debug for DisplayUnionType<'_> { +impl Display for DisplayUnionType<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + +impl fmt::Debug for DisplayUnionType<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(self, f) } @@ -1618,11 +1857,11 @@ impl Display for DisplayLiteralGroup<'_> { } impl<'db> IntersectionType<'db> { - fn display_with( - &'db self, + fn display_with<'a>( + &'a self, db: &'db dyn Db, settings: DisplaySettings<'db>, - ) -> DisplayIntersectionType<'db> { + ) -> DisplayIntersectionType<'a, 'db> { DisplayIntersectionType { db, ty: self, @@ -1631,14 +1870,14 @@ impl<'db> IntersectionType<'db> { } } -struct DisplayIntersectionType<'db> { - ty: &'db IntersectionType<'db>, +struct DisplayIntersectionType<'a, 'db> { + ty: &'a IntersectionType<'db>, db: &'db dyn Db, settings: DisplaySettings<'db>, } -impl Display for DisplayIntersectionType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayIntersectionType<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { let tys = self .ty .positive(self.db) @@ -1660,11 +1899,18 @@ impl Display for DisplayIntersectionType<'_> { negated: true, }), ); + f.join(" & ").entries(tys).finish() } } -impl fmt::Debug for DisplayIntersectionType<'_> { +impl Display for DisplayIntersectionType<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + +impl fmt::Debug for DisplayIntersectionType<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(self, f) } @@ -1677,8 +1923,8 @@ struct DisplayMaybeNegatedType<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayMaybeNegatedType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayMaybeNegatedType<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { if self.negated { f.write_str("~")?; } @@ -1687,7 +1933,13 @@ impl Display for DisplayMaybeNegatedType<'_> { db: self.db, settings: self.settings.clone(), } - .fmt(f) + .fmt_detailed(f) + } +} + +impl Display for DisplayMaybeNegatedType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) } } @@ -1697,14 +1949,14 @@ struct DisplayMaybeParenthesizedType<'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayMaybeParenthesizedType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let write_parentheses = |f: &mut Formatter<'_>| { - write!( - f, - "({})", - self.ty.display_with(self.db, self.settings.clone()) - ) +impl<'db> FmtDetailed<'db> for DisplayMaybeParenthesizedType<'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { + let write_parentheses = |f: &mut TypeWriter<'_, '_, 'db>| { + f.write_char('(')?; + self.ty + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f)?; + f.write_char(')') }; match self.ty { Type::Callable(_) @@ -1715,11 +1967,20 @@ impl Display for DisplayMaybeParenthesizedType<'_> { Type::Intersection(intersection) if !intersection.has_one_element(self.db) => { write_parentheses(f) } - _ => self.ty.display_with(self.db, self.settings.clone()).fmt(f), + _ => self + .ty + .display_with(self.db, self.settings.clone()) + .fmt_detailed(f), } } } +impl Display for DisplayMaybeParenthesizedType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + pub(crate) trait TypeArrayDisplay<'db> { fn display_with( &self, @@ -1776,8 +2037,8 @@ pub(crate) struct DisplayTypeArray<'b, 'db> { settings: DisplaySettings<'db>, } -impl Display for DisplayTypeArray<'_, '_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl<'db> FmtDetailed<'db> for DisplayTypeArray<'_, 'db> { + fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { f.join(", ") .entries( self.types @@ -1788,9 +2049,15 @@ impl Display for DisplayTypeArray<'_, '_> { } } +impl Display for DisplayTypeArray<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_detailed(&mut TypeWriter::Formatter(f)) + } +} + impl<'db> StringLiteralType<'db> { fn display_with( - &'db self, + self, db: &'db dyn Db, settings: DisplaySettings<'db>, ) -> DisplayStringLiteralType<'db> { diff --git a/crates/ty_server/tests/e2e/inlay_hints.rs b/crates/ty_server/tests/e2e/inlay_hints.rs index 42152a7351..3ee77ed7e4 100644 --- a/crates/ty_server/tests/e2e/inlay_hints.rs +++ b/crates/ty_server/tests/e2e/inlay_hints.rs @@ -47,7 +47,20 @@ y = foo(1) "value": ": " }, { - "value": "int" + "value": "int", + "location": { + "uri": "file:///stdlib/builtins.pyi", + "range": { + "start": { + "line": 347, + "character": 6 + }, + "end": { + "line": 347, + "character": 9 + } + } + } } ], "kind": 1 diff --git a/crates/ty_server/tests/e2e/main.rs b/crates/ty_server/tests/e2e/main.rs index 97f0d69573..086d24dcc5 100644 --- a/crates/ty_server/tests/e2e/main.rs +++ b/crates/ty_server/tests/e2e/main.rs @@ -1198,6 +1198,7 @@ impl TestContext { r#"The system cannot find the file specified."#, "No such file or directory", ); + settings.add_filter(r"file://.*/stdlib/", "file:///stdlib/"); let settings_scope = settings.bind_to_scope();