mirror of https://github.com/astral-sh/ruff
[ty] Double click to insert inlay hint (#21600)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary Resolves https://github.com/astral-sh/ty/issues/317#issuecomment-3567398107. I can't get the auto import working great. I haven't added many places where we specify that the type display is invalid syntax. ## Test Plan Nothing yet
This commit is contained in:
parent
0c6d652b5f
commit
6f9265d78d
|
|
@ -6,8 +6,8 @@ use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, ExprUnaryOp, Stmt, UnaryOp};
|
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, ExprUnaryOp, Stmt, UnaryOp};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
use ty_python_semantic::types::Type;
|
|
||||||
use ty_python_semantic::types::ide_support::inlay_hint_call_argument_details;
|
use ty_python_semantic::types::ide_support::inlay_hint_call_argument_details;
|
||||||
|
use ty_python_semantic::types::{Type, TypeDetail};
|
||||||
use ty_python_semantic::{HasType, SemanticModel};
|
use ty_python_semantic::{HasType, SemanticModel};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -15,10 +15,12 @@ pub struct InlayHint {
|
||||||
pub position: TextSize,
|
pub position: TextSize,
|
||||||
pub kind: InlayHintKind,
|
pub kind: InlayHintKind,
|
||||||
pub label: InlayHintLabel,
|
pub label: InlayHintLabel,
|
||||||
|
pub text_edits: Vec<InlayHintTextEdit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlayHint {
|
impl InlayHint {
|
||||||
fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self {
|
fn variable_type(expr: &Expr, ty: Type, db: &dyn Db, allow_edits: bool) -> Self {
|
||||||
|
let position = expr.range().end();
|
||||||
// Render the type to a string, and get subspans for all the types that make it up
|
// 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();
|
let details = ty.display(db).to_string_parts();
|
||||||
|
|
||||||
|
|
@ -34,7 +36,7 @@ impl InlayHint {
|
||||||
let mut label_parts = vec![": ".into()];
|
let mut label_parts = vec![": ".into()];
|
||||||
for (target, detail) in details.targets.iter().zip(&details.details) {
|
for (target, detail) in details.targets.iter().zip(&details.details) {
|
||||||
match detail {
|
match detail {
|
||||||
ty_python_semantic::types::TypeDetail::Type(ty) => {
|
TypeDetail::Type(ty) => {
|
||||||
let start = target.start().to_usize();
|
let start = target.start().to_usize();
|
||||||
let end = target.end().to_usize();
|
let end = target.end().to_usize();
|
||||||
// If we skipped over some bytes, push them with no target
|
// If we skipped over some bytes, push them with no target
|
||||||
|
|
@ -50,9 +52,9 @@ impl InlayHint {
|
||||||
offset = end;
|
offset = end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ty_python_semantic::types::TypeDetail::SignatureStart
|
TypeDetail::SignatureStart
|
||||||
| ty_python_semantic::types::TypeDetail::SignatureEnd
|
| TypeDetail::SignatureEnd
|
||||||
| ty_python_semantic::types::TypeDetail::Parameter(_) => {
|
| TypeDetail::Parameter(_) => {
|
||||||
// Don't care about these
|
// Don't care about these
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,10 +64,20 @@ impl InlayHint {
|
||||||
label_parts.push(details.label[offset..details.label.len()].into());
|
label_parts.push(details.label[offset..details.label.len()].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text_edits = if details.is_valid_syntax && allow_edits {
|
||||||
|
vec![InlayHintTextEdit {
|
||||||
|
range: TextRange::new(position, position),
|
||||||
|
new_text: format!(": {}", details.label),
|
||||||
|
}]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
kind: InlayHintKind::Type,
|
kind: InlayHintKind::Type,
|
||||||
label: InlayHintLabel { parts: label_parts },
|
label: InlayHintLabel { parts: label_parts },
|
||||||
|
text_edits,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,6 +95,7 @@ impl InlayHint {
|
||||||
position,
|
position,
|
||||||
kind: InlayHintKind::CallArgumentName,
|
kind: InlayHintKind::CallArgumentName,
|
||||||
label: InlayHintLabel { parts: label_parts },
|
label: InlayHintLabel { parts: label_parts },
|
||||||
|
text_edits: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +188,12 @@ impl From<&str> for InlayHintLabelPart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InlayHintTextEdit {
|
||||||
|
pub range: TextRange,
|
||||||
|
pub new_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inlay_hints(
|
pub fn inlay_hints(
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
file: File,
|
file: File,
|
||||||
|
|
@ -234,6 +253,7 @@ struct InlayHintVisitor<'a, 'db> {
|
||||||
in_assignment: bool,
|
in_assignment: bool,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
settings: &'a InlayHintSettings,
|
settings: &'a InlayHintSettings,
|
||||||
|
in_no_edits_allowed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||||
|
|
@ -245,15 +265,16 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||||
in_assignment: false,
|
in_assignment: false,
|
||||||
range,
|
range,
|
||||||
settings,
|
settings,
|
||||||
|
in_no_edits_allowed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_type_hint(&mut self, position: TextSize, ty: Type<'db>) {
|
fn add_type_hint(&mut self, expr: &Expr, ty: Type<'db>, allow_edits: bool) {
|
||||||
if !self.settings.variable_types {
|
if !self.settings.variable_types {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inlay_hint = InlayHint::variable_type(position, ty, self.db);
|
let inlay_hint = InlayHint::variable_type(expr, ty, self.db, allow_edits);
|
||||||
|
|
||||||
self.hints.push(inlay_hint);
|
self.hints.push(inlay_hint);
|
||||||
}
|
}
|
||||||
|
|
@ -297,9 +318,13 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Assign(assign) => {
|
Stmt::Assign(assign) => {
|
||||||
self.in_assignment = !type_hint_is_excessive_for_expr(&assign.value);
|
self.in_assignment = !type_hint_is_excessive_for_expr(&assign.value);
|
||||||
|
if !annotations_are_valid_syntax(assign) {
|
||||||
|
self.in_no_edits_allowed = true;
|
||||||
|
}
|
||||||
for target in &assign.targets {
|
for target in &assign.targets {
|
||||||
self.visit_expr(target);
|
self.visit_expr(target);
|
||||||
}
|
}
|
||||||
|
self.in_no_edits_allowed = false;
|
||||||
self.in_assignment = false;
|
self.in_assignment = false;
|
||||||
|
|
||||||
self.visit_expr(&assign.value);
|
self.visit_expr(&assign.value);
|
||||||
|
|
@ -325,7 +350,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||||
if self.in_assignment {
|
if self.in_assignment {
|
||||||
if name.ctx.is_store() {
|
if name.ctx.is_store() {
|
||||||
let ty = expr.inferred_type(&self.model);
|
let ty = expr.inferred_type(&self.model);
|
||||||
self.add_type_hint(expr.range().end(), ty);
|
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source_order::walk_expr(self, expr);
|
source_order::walk_expr(self, expr);
|
||||||
|
|
@ -334,7 +359,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||||
if self.in_assignment {
|
if self.in_assignment {
|
||||||
if attribute.ctx.is_store() {
|
if attribute.ctx.is_store() {
|
||||||
let ty = expr.inferred_type(&self.model);
|
let ty = expr.inferred_type(&self.model);
|
||||||
self.add_type_hint(expr.range().end(), ty);
|
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source_order::walk_expr(self, expr);
|
source_order::walk_expr(self, expr);
|
||||||
|
|
@ -420,6 +445,22 @@ fn type_hint_is_excessive_for_expr(expr: &Expr) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn annotations_are_valid_syntax(stmt_assign: &ruff_python_ast::StmtAssign) -> bool {
|
||||||
|
if stmt_assign.targets.len() > 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if stmt_assign
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.any(|target| matches!(target, Expr::Tuple(_)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -427,6 +468,7 @@ mod tests {
|
||||||
use crate::NavigationTarget;
|
use crate::NavigationTarget;
|
||||||
use crate::tests::IntoDiagnostic;
|
use crate::tests::IntoDiagnostic;
|
||||||
use insta::{assert_snapshot, internals::SettingsBindDropGuard};
|
use insta::{assert_snapshot, internals::SettingsBindDropGuard};
|
||||||
|
use itertools::Itertools;
|
||||||
use ruff_db::{
|
use ruff_db::{
|
||||||
diagnostic::{
|
diagnostic::{
|
||||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
|
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
|
||||||
|
|
@ -517,12 +559,15 @@ mod tests {
|
||||||
fn inlay_hints_with_settings(&mut 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 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 inlay_hint_buf = source_text(&self.db, self.file).as_str().to_string();
|
||||||
|
let mut text_edit_buf = inlay_hint_buf.clone();
|
||||||
|
|
||||||
let mut tbd_diagnostics = Vec::new();
|
let mut tbd_diagnostics = Vec::new();
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
|
let mut edit_offset = 0;
|
||||||
|
|
||||||
for hint in hints {
|
for hint in hints {
|
||||||
let end_position = hint.position.to_usize() + offset;
|
let end_position = hint.position.to_usize() + offset;
|
||||||
let mut hint_str = "[".to_string();
|
let mut hint_str = "[".to_string();
|
||||||
|
|
@ -538,36 +583,65 @@ mod tests {
|
||||||
hint_str.push_str(part.text());
|
hint_str.push_str(part.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for edit in hint.text_edits {
|
||||||
|
let start = edit.range.start().to_usize() + edit_offset;
|
||||||
|
let end = edit.range.end().to_usize() + edit_offset;
|
||||||
|
|
||||||
|
text_edit_buf.replace_range(start..end, &edit.new_text);
|
||||||
|
|
||||||
|
if start == end {
|
||||||
|
edit_offset += edit.new_text.len();
|
||||||
|
} else {
|
||||||
|
edit_offset += edit.new_text.len() - edit.range.len().to_usize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hint_str.push(']');
|
hint_str.push(']');
|
||||||
offset += hint_str.len();
|
offset += hint_str.len();
|
||||||
|
|
||||||
buf.insert_str(end_position, &hint_str);
|
inlay_hint_buf.insert_str(end_position, &hint_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db.write_file("main2.py", &buf).unwrap();
|
self.db.write_file("main2.py", &inlay_hint_buf).unwrap();
|
||||||
let inlayed_file =
|
let inlayed_file =
|
||||||
system_path_to_file(&self.db, "main2.py").expect("newly written file to existing");
|
system_path_to_file(&self.db, "main2.py").expect("newly written file to existing");
|
||||||
|
|
||||||
let diagnostics = tbd_diagnostics.into_iter().map(|(label_range, target)| {
|
let location_diagnostics = tbd_diagnostics.into_iter().map(|(label_range, target)| {
|
||||||
InlayHintLocationDiagnostic::new(FileRange::new(inlayed_file, label_range), &target)
|
InlayHintLocationDiagnostic::new(FileRange::new(inlayed_file, label_range), &target)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut rendered_diagnostics = self.render_diagnostics(diagnostics);
|
let mut rendered_diagnostics = location_diagnostics
|
||||||
|
.map(|diagnostic| self.render_diagnostic(diagnostic))
|
||||||
|
.join("");
|
||||||
|
|
||||||
if !rendered_diagnostics.is_empty() {
|
if !rendered_diagnostics.is_empty() {
|
||||||
rendered_diagnostics = format!(
|
rendered_diagnostics = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
crate::MarkupKind::PlainText.horizontal_line(),
|
crate::MarkupKind::PlainText.horizontal_line(),
|
||||||
rendered_diagnostics
|
rendered_diagnostics
|
||||||
|
.strip_suffix("\n")
|
||||||
|
.unwrap_or(&rendered_diagnostics)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
format!("{buf}{rendered_diagnostics}",)
|
let rendered_edit_diagnostic = if edit_offset != 0 {
|
||||||
|
let edit_diagnostic = InlayHintEditDiagnostic::new(text_edit_buf);
|
||||||
|
let text_edit_buf = self.render_diagnostic(edit_diagnostic);
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
crate::MarkupKind::PlainText.horizontal_line(),
|
||||||
|
text_edit_buf
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{inlay_hint_buf}{rendered_diagnostics}{rendered_edit_diagnostic}",)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_diagnostics<I, D>(&self, diagnostics: I) -> String
|
fn render_diagnostic<D>(&self, diagnostic: D) -> String
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = D>,
|
|
||||||
D: IntoDiagnostic,
|
D: IntoDiagnostic,
|
||||||
{
|
{
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
@ -578,10 +652,8 @@ mod tests {
|
||||||
.color(false)
|
.color(false)
|
||||||
.format(DiagnosticFormat::Full);
|
.format(DiagnosticFormat::Full);
|
||||||
|
|
||||||
for diagnostic in diagnostics {
|
let diag = diagnostic.into_diagnostic();
|
||||||
let diag = diagnostic.into_diagnostic();
|
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
|
||||||
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
@ -728,6 +800,20 @@ mod tests {
|
||||||
10 | bb[: Literal[b"foo"]] = aa
|
10 | bb[: Literal[b"foo"]] = aa
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def i(x: int, /) -> int:
|
||||||
|
return x
|
||||||
|
|
||||||
|
x = 1
|
||||||
|
y: Literal[1] = x
|
||||||
|
z: int = i(1)
|
||||||
|
w: int = z
|
||||||
|
aa = b'foo'
|
||||||
|
bb: Literal[b"foo"] = aa
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1321,6 +1407,20 @@ mod tests {
|
||||||
10 | w[: tuple[int, str]] = z
|
10 | w[: tuple[int, str]] = z
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def i(x: int, /) -> int:
|
||||||
|
return x
|
||||||
|
def s(x: str, /) -> str:
|
||||||
|
return x
|
||||||
|
|
||||||
|
x = (1, 'abc')
|
||||||
|
y: tuple[Literal[1], Literal["abc"]] = x
|
||||||
|
z: tuple[int, str] = (i(1), s('abc'))
|
||||||
|
w: tuple[int, str] = z
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1654,6 +1754,18 @@ mod tests {
|
||||||
8 | w[: int] = z
|
8 | w[: int] = z
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def i(x: int, /) -> int:
|
||||||
|
return x
|
||||||
|
|
||||||
|
x: int = 1
|
||||||
|
y: Literal[1] = x
|
||||||
|
z: int = i(1)
|
||||||
|
w: int = z
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1691,6 +1803,15 @@ mod tests {
|
||||||
| ^^^
|
| ^^^
|
||||||
5 | z = x
|
5 | z = x
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def i(x: int, /) -> int:
|
||||||
|
return x
|
||||||
|
x: int = i(1)
|
||||||
|
z = x
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1810,6 +1931,18 @@ mod tests {
|
||||||
8 | a.y[: int] = int(3)
|
8 | a.y[: int] = int(3)
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def __init__(self, y):
|
||||||
|
self.x: int = int(1)
|
||||||
|
self.y: Unknown = y
|
||||||
|
|
||||||
|
a: A = A(2)
|
||||||
|
a.y: int = int(3)
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2640,6 +2773,22 @@ mod tests {
|
||||||
12 | k[: list[Unknown | int | float]] = [-1, -2.0]
|
12 | k[: list[Unknown | int | float]] = [-1, -2.0]
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
a: list[Unknown | int] = [1, 2]
|
||||||
|
b: list[Unknown | float] = [1.0, 2.0]
|
||||||
|
c: list[Unknown | bool] = [True, False]
|
||||||
|
d: list[Unknown | None] = [None, None]
|
||||||
|
e: list[Unknown | str] = ["hel", "lo"]
|
||||||
|
f: list[Unknown | str] = ['the', 're']
|
||||||
|
g: list[Unknown | str] = [f"{ft}", f"{ft}"]
|
||||||
|
h: list[Unknown | Template] = [t"wow %d", t"wow %d"]
|
||||||
|
i: list[Unknown | bytes] = [b'/x01', b'/x02']
|
||||||
|
j: list[Unknown | int | float] = [+1, +2.0]
|
||||||
|
k: list[Unknown | int | float] = [-1, -2.0]
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2811,6 +2960,19 @@ mod tests {
|
||||||
9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass())
|
9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
def __init__(self):
|
||||||
|
self.x: int = 1
|
||||||
|
|
||||||
|
x: MyClass = MyClass()
|
||||||
|
y: tuple[MyClass, MyClass] = (MyClass(), MyClass())
|
||||||
|
a, b = MyClass(), MyClass()
|
||||||
|
c, d = (MyClass(), MyClass())
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3681,6 +3843,20 @@ mod tests {
|
||||||
10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "…
|
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-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class MyClass[T, U]:
|
||||||
|
def __init__(self, x: list[T], y: tuple[U, U]):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
x: MyClass[Unknown | int, str] = MyClass([42], ("a", "b"))
|
||||||
|
y: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]] = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b")))
|
||||||
|
a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))
|
||||||
|
c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b")))
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3836,6 +4012,20 @@ mod tests {
|
||||||
10 | foo([x=]val.y)
|
10 | foo([x=]val.y)
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def foo(x: int): pass
|
||||||
|
class MyClass:
|
||||||
|
def __init__(self):
|
||||||
|
self.x: int = 1
|
||||||
|
self.y: int = 2
|
||||||
|
val: MyClass = MyClass()
|
||||||
|
|
||||||
|
foo(val.x)
|
||||||
|
foo(val.y)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3901,6 +4091,20 @@ mod tests {
|
||||||
10 | foo([x=]x.y)
|
10 | foo([x=]x.y)
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def foo(x: int): pass
|
||||||
|
class MyClass:
|
||||||
|
def __init__(self):
|
||||||
|
self.x: int = 1
|
||||||
|
self.y: int = 2
|
||||||
|
x: MyClass = MyClass()
|
||||||
|
|
||||||
|
foo(x.x)
|
||||||
|
foo(x.y)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3969,6 +4173,22 @@ mod tests {
|
||||||
12 | foo([x=]val.y())
|
12 | foo([x=]val.y())
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def foo(x: int): pass
|
||||||
|
class MyClass:
|
||||||
|
def __init__(self):
|
||||||
|
def x() -> int:
|
||||||
|
return 1
|
||||||
|
def y() -> int:
|
||||||
|
return 2
|
||||||
|
val: MyClass = MyClass()
|
||||||
|
|
||||||
|
foo(val.x())
|
||||||
|
foo(val.y())
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4043,6 +4263,24 @@ mod tests {
|
||||||
14 | foo([x=]val.y()[1])
|
14 | foo([x=]val.y()[1])
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def foo(x: int): pass
|
||||||
|
class MyClass:
|
||||||
|
def __init__(self):
|
||||||
|
def x() -> List[int]:
|
||||||
|
return 1
|
||||||
|
def y() -> List[int]:
|
||||||
|
return 2
|
||||||
|
val: MyClass = MyClass()
|
||||||
|
|
||||||
|
foo(val.x()[0])
|
||||||
|
foo(val.y()[1])
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4193,6 +4431,17 @@ mod tests {
|
||||||
7 | foo([x=]y[0])
|
7 | foo([x=]y[0])
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def foo(x: int): pass
|
||||||
|
x: list[Unknown | int] = [1]
|
||||||
|
y: list[Unknown | int] = [2]
|
||||||
|
|
||||||
|
foo(x[0])
|
||||||
|
foo(y[0])
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4378,6 +4627,15 @@ mod tests {
|
||||||
5 | f[: Foo] = Foo([x=]1)
|
5 | f[: Foo] = Foo([x=]1)
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
def __init__(self, x: int): pass
|
||||||
|
Foo(1)
|
||||||
|
f: Foo = Foo(1)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4450,6 +4708,15 @@ mod tests {
|
||||||
5 | f[: Foo] = Foo([x=]1)
|
5 | f[: Foo] = Foo([x=]1)
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
def __new__(cls, x: int): pass
|
||||||
|
Foo(1)
|
||||||
|
f: Foo = Foo(1)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5197,6 +5464,15 @@ mod tests {
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
5 | my_func(x="hello")
|
5 | my_func(x="hello")
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
from typing import LiteralString
|
||||||
|
def my_func(x: LiteralString):
|
||||||
|
y: LiteralString = x
|
||||||
|
my_func(x="hello")
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5339,6 +5615,23 @@ mod tests {
|
||||||
13 | y[: Literal[1, 2, 3, "hello"] | None] = x
|
13 | y[: Literal[1, 2, 3, "hello"] | None] = x
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def branch(cond: int):
|
||||||
|
if cond < 10:
|
||||||
|
x = 1
|
||||||
|
elif cond < 20:
|
||||||
|
x = 2
|
||||||
|
elif cond < 30:
|
||||||
|
x = 3
|
||||||
|
elif cond < 40:
|
||||||
|
x = "hello"
|
||||||
|
else:
|
||||||
|
x = None
|
||||||
|
y: Literal[1, 2, 3, "hello"] | None = x
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5454,6 +5747,13 @@ mod tests {
|
||||||
3 | y[: type[list[str]]] = type(x)
|
3 | y[: type[list[str]]] = type(x)
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
def f(x: list[str]):
|
||||||
|
y: type[list[str]] = type(x)
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5493,6 +5793,16 @@ mod tests {
|
||||||
6 | ab[: property] = F.whatever
|
6 | ab[: property] = F.whatever
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
|
|
|
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-edit]: File after edits
|
||||||
|
info: Source
|
||||||
|
|
||||||
|
class F:
|
||||||
|
@property
|
||||||
|
def whatever(self): ...
|
||||||
|
|
||||||
|
ab: property = F.whatever
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5810,6 +6120,180 @@ mod tests {
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_signature_inlay_hint() {
|
||||||
|
let mut test = inlay_hint_test(
|
||||||
|
"
|
||||||
|
def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
|
||||||
|
a = foo",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.inlay_hints(), @r#"
|
||||||
|
def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
|
||||||
|
a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
---------------------------------------------
|
||||||
|
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:16
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
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:25
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
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:4:37
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
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:43
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
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:49
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
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:4:54
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
|
||||||
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main2.py:4:63
|
||||||
|
|
|
||||||
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||||
|
3 |
|
||||||
|
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||||
|
| ^^^^^^^
|
||||||
|
|
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_module_inlay_hint() {
|
||||||
|
let mut test = inlay_hint_test(
|
||||||
|
"
|
||||||
|
import foo
|
||||||
|
|
||||||
|
a = foo",
|
||||||
|
);
|
||||||
|
|
||||||
|
test.with_extra_file("foo.py", "'''Foo module'''");
|
||||||
|
|
||||||
|
assert_snapshot!(test.inlay_hints(), @r"
|
||||||
|
import foo
|
||||||
|
|
||||||
|
a[: <module 'foo'>] = foo
|
||||||
|
---------------------------------------------
|
||||||
|
info[inlay-hint-location]: Inlay Hint Target
|
||||||
|
--> foo.py:1:1
|
||||||
|
|
|
||||||
|
1 | '''Foo module'''
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main2.py:4:5
|
||||||
|
|
|
||||||
|
2 | import foo
|
||||||
|
3 |
|
||||||
|
4 | a[: <module 'foo'>] = foo
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
struct InlayHintLocationDiagnostic {
|
struct InlayHintLocationDiagnostic {
|
||||||
source: FileRange,
|
source: FileRange,
|
||||||
target: FileRange,
|
target: FileRange,
|
||||||
|
|
@ -5847,4 +6331,31 @@ mod tests {
|
||||||
main
|
main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InlayHintEditDiagnostic {
|
||||||
|
file_content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlayHintEditDiagnostic {
|
||||||
|
fn new(file_content: String) -> Self {
|
||||||
|
Self { file_content }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDiagnostic for InlayHintEditDiagnostic {
|
||||||
|
fn into_diagnostic(self) -> Diagnostic {
|
||||||
|
let mut main = Diagnostic::new(
|
||||||
|
DiagnosticId::Lint(LintName::of("inlay-hint-edit")),
|
||||||
|
Severity::Info,
|
||||||
|
"File after edits".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
main.sub(SubDiagnostic::new(
|
||||||
|
SubDiagnosticSeverity::Info,
|
||||||
|
format!("{}\n{}", "Source", self.file_content),
|
||||||
|
));
|
||||||
|
|
||||||
|
main
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ pub use document_symbols::document_symbols;
|
||||||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||||
pub use goto_references::goto_references;
|
pub use goto_references::goto_references;
|
||||||
pub use hover::hover;
|
pub use hover::hover;
|
||||||
pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints};
|
pub use inlay_hints::{
|
||||||
|
InlayHintKind, InlayHintLabel, InlayHintSettings, InlayHintTextEdit, inlay_hints,
|
||||||
|
};
|
||||||
pub use markup::MarkupKind;
|
pub use markup::MarkupKind;
|
||||||
pub use references::ReferencesMode;
|
pub use references::ReferencesMode;
|
||||||
pub use rename::{can_rename, rename};
|
pub use rename::{can_rename, rename};
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ pub struct TypeDisplayDetails<'db> {
|
||||||
pub targets: Vec<TextRange>,
|
pub targets: Vec<TextRange>,
|
||||||
/// Metadata for each range
|
/// Metadata for each range
|
||||||
pub details: Vec<TypeDetail<'db>>,
|
pub details: Vec<TypeDetail<'db>>,
|
||||||
|
/// Whether the label is valid Python syntax
|
||||||
|
pub is_valid_syntax: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstraction over "are we doing normal formatting, or tracking ranges with metadata?"
|
/// Abstraction over "are we doing normal formatting, or tracking ranges with metadata?"
|
||||||
|
|
@ -119,6 +121,7 @@ struct TypeDetailsWriter<'db> {
|
||||||
label: String,
|
label: String,
|
||||||
targets: Vec<TextRange>,
|
targets: Vec<TextRange>,
|
||||||
details: Vec<TypeDetail<'db>>,
|
details: Vec<TypeDetail<'db>>,
|
||||||
|
is_valid_syntax: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> TypeDetailsWriter<'db> {
|
impl<'db> TypeDetailsWriter<'db> {
|
||||||
|
|
@ -127,6 +130,7 @@ impl<'db> TypeDetailsWriter<'db> {
|
||||||
label: String::new(),
|
label: String::new(),
|
||||||
targets: Vec::new(),
|
targets: Vec::new(),
|
||||||
details: Vec::new(),
|
details: Vec::new(),
|
||||||
|
is_valid_syntax: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +140,7 @@ impl<'db> TypeDetailsWriter<'db> {
|
||||||
label: self.label,
|
label: self.label,
|
||||||
targets: self.targets,
|
targets: self.targets,
|
||||||
details: self.details,
|
details: self.details,
|
||||||
|
is_valid_syntax: self.is_valid_syntax,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +197,13 @@ impl<'a, 'b, 'db> TypeWriter<'a, 'b, 'db> {
|
||||||
self.with_detail(TypeDetail::Type(ty))
|
self.with_detail(TypeDetail::Type(ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_invalid_syntax(&mut self) {
|
||||||
|
match self {
|
||||||
|
TypeWriter::Formatter(_) => {}
|
||||||
|
TypeWriter::Details(details) => details.is_valid_syntax = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn join<'c>(&'c mut self, separator: &'static str) -> Join<'a, 'b, 'c, 'db> {
|
fn join<'c>(&'c mut self, separator: &'static str) -> Join<'a, 'b, 'c, 'db> {
|
||||||
Join {
|
Join {
|
||||||
fmt: self,
|
fmt: self,
|
||||||
|
|
@ -539,6 +551,7 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||||
let line_index = line_index(self.db, file);
|
let line_index = line_index(self.db, file);
|
||||||
let class_offset = self.class.header_range(self.db).start();
|
let class_offset = self.class.header_range(self.db).start();
|
||||||
let line_number = line_index.line_index(class_offset);
|
let line_number = line_index.line_index(class_offset);
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(f, " @ {path}:{line_number}")?;
|
write!(f, " @ {path}:{line_number}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -599,6 +612,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
.fmt_detailed(f),
|
.fmt_detailed(f),
|
||||||
},
|
},
|
||||||
Protocol::Synthesized(synthetic) => {
|
Protocol::Synthesized(synthetic) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_char('<')?;
|
f.write_char('<')?;
|
||||||
f.with_type(Type::SpecialForm(SpecialFormType::Protocol))
|
f.with_type(Type::SpecialForm(SpecialFormType::Protocol))
|
||||||
.write_str("Protocol")?;
|
.write_str("Protocol")?;
|
||||||
|
|
@ -618,6 +632,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
},
|
},
|
||||||
Type::PropertyInstance(_) => f.with_type(self.ty).write_str("property"),
|
Type::PropertyInstance(_) => f.with_type(self.ty).write_str("property"),
|
||||||
Type::ModuleLiteral(module) => {
|
Type::ModuleLiteral(module) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(
|
write!(
|
||||||
f.with_type(self.ty),
|
f.with_type(self.ty),
|
||||||
"<module '{}'>",
|
"<module '{}'>",
|
||||||
|
|
@ -625,6 +640,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::ClassLiteral(class) => {
|
Type::ClassLiteral(class) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
let mut f = f.with_type(self.ty);
|
let mut f = f.with_type(self.ty);
|
||||||
f.write_str("<class '")?;
|
f.write_str("<class '")?;
|
||||||
class
|
class
|
||||||
|
|
@ -633,6 +649,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
f.write_str("'>")
|
f.write_str("'>")
|
||||||
}
|
}
|
||||||
Type::GenericAlias(generic) => {
|
Type::GenericAlias(generic) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
let mut f = f.with_type(self.ty);
|
let mut f = f.with_type(self.ty);
|
||||||
f.write_str("<class '")?;
|
f.write_str("<class '")?;
|
||||||
generic
|
generic
|
||||||
|
|
@ -691,7 +708,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
db: self.db,
|
db: self.db,
|
||||||
settings: self.settings.clone(),
|
settings: self.settings.clone(),
|
||||||
};
|
};
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("bound method ")?;
|
f.write_str("bound method ")?;
|
||||||
self_ty
|
self_ty
|
||||||
.display_with(self.db, self.settings.singleline())
|
.display_with(self.db, self.settings.singleline())
|
||||||
|
|
@ -729,51 +746,57 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::FunctionTypeDunderGet(function)) => {
|
Type::KnownBoundMethod(method_type) => {
|
||||||
write!(
|
f.set_invalid_syntax();
|
||||||
f,
|
match method_type {
|
||||||
"<method-wrapper `__get__` of `{function}`>",
|
KnownBoundMethodType::FunctionTypeDunderGet(function) => {
|
||||||
function = function.name(self.db),
|
write!(
|
||||||
)
|
f,
|
||||||
|
"<method-wrapper `__get__` of `{function}`>",
|
||||||
|
function = function.name(self.db),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::FunctionTypeDunderCall(function) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"<method-wrapper `__call__` of `{function}`>",
|
||||||
|
function = function.name(self.db),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::PropertyDunderGet(_) => {
|
||||||
|
f.write_str("<method-wrapper `__get__` of `property` object>")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::PropertyDunderSet(_) => {
|
||||||
|
f.write_str("<method-wrapper `__set__` of `property` object>")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::StrStartswith(_) => {
|
||||||
|
f.write_str("<method-wrapper `startswith` of `str` object>")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetRange => {
|
||||||
|
f.write_str("bound method `ConstraintSet.range`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetAlways => {
|
||||||
|
f.write_str("bound method `ConstraintSet.always`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetNever => {
|
||||||
|
f.write_str("bound method `ConstraintSet.never`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {
|
||||||
|
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetSatisfies(_) => {
|
||||||
|
f.write_str("bound method `ConstraintSet.satisfies`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
|
||||||
|
f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`")
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::GenericContextSpecializeConstrained(_) => {
|
||||||
|
f.write_str("bound method `GenericContext.specialize_constrained`")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::FunctionTypeDunderCall(function)) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"<method-wrapper `__call__` of `{function}`>",
|
|
||||||
function = function.name(self.db),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderGet(_)) => {
|
|
||||||
f.write_str("<method-wrapper `__get__` of `property` object>")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderSet(_)) => {
|
|
||||||
f.write_str("<method-wrapper `__set__` of `property` object>")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => {
|
|
||||||
f.write_str("<method-wrapper `startswith` of `str` object>")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
|
|
||||||
f.write_str("bound method `ConstraintSet.range`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetAlways) => {
|
|
||||||
f.write_str("bound method `ConstraintSet.always`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => {
|
|
||||||
f.write_str("bound method `ConstraintSet.never`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => {
|
|
||||||
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetSatisfies(_)) => {
|
|
||||||
f.write_str("bound method `ConstraintSet.satisfies`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(
|
|
||||||
_,
|
|
||||||
)) => f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`"),
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::GenericContextSpecializeConstrained(
|
|
||||||
_,
|
|
||||||
)) => f.write_str("bound method `GenericContext.specialize_constrained`"),
|
|
||||||
Type::WrapperDescriptor(kind) => {
|
Type::WrapperDescriptor(kind) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
let (method, object) = match kind {
|
let (method, object) = match kind {
|
||||||
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),
|
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),
|
||||||
WrapperDescriptorKind::PropertyDunderGet => ("__get__", "property"),
|
WrapperDescriptorKind::PropertyDunderGet => ("__get__", "property"),
|
||||||
|
|
@ -782,9 +805,11 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
write!(f, "<wrapper-descriptor `{method}` of `{object}` objects>")
|
write!(f, "<wrapper-descriptor `{method}` of `{object}` objects>")
|
||||||
}
|
}
|
||||||
Type::DataclassDecorator(_) => {
|
Type::DataclassDecorator(_) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("<decorator produced by dataclass-like function>")
|
f.write_str("<decorator produced by dataclass-like function>")
|
||||||
}
|
}
|
||||||
Type::DataclassTransformer(_) => {
|
Type::DataclassTransformer(_) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("<decorator produced by typing.dataclass_transform>")
|
f.write_str("<decorator produced by typing.dataclass_transform>")
|
||||||
}
|
}
|
||||||
Type::Union(union) => union
|
Type::Union(union) => union
|
||||||
|
|
@ -828,11 +853,13 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
write!(f, ".{}", enum_literal.name(self.db))
|
write!(f, ".{}", enum_literal.name(self.db))
|
||||||
}
|
}
|
||||||
Type::TypeVar(bound_typevar) => {
|
Type::TypeVar(bound_typevar) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
|
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
|
||||||
}
|
}
|
||||||
Type::AlwaysTruthy => f.with_type(self.ty).write_str("AlwaysTruthy"),
|
Type::AlwaysTruthy => f.with_type(self.ty).write_str("AlwaysTruthy"),
|
||||||
Type::AlwaysFalsy => f.with_type(self.ty).write_str("AlwaysFalsy"),
|
Type::AlwaysFalsy => f.with_type(self.ty).write_str("AlwaysFalsy"),
|
||||||
Type::BoundSuper(bound_super) => {
|
Type::BoundSuper(bound_super) => {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("<super: ")?;
|
f.write_str("<super: ")?;
|
||||||
Type::from(bound_super.pivot_class(self.db))
|
Type::from(bound_super.pivot_class(self.db))
|
||||||
.display_with(self.db, self.settings.singleline())
|
.display_with(self.db, self.settings.singleline())
|
||||||
|
|
@ -852,6 +879,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
.display_with(self.db, self.settings.singleline())
|
.display_with(self.db, self.settings.singleline())
|
||||||
.fmt_detailed(f)?;
|
.fmt_detailed(f)?;
|
||||||
if let Some(name) = type_is.place_name(self.db) {
|
if let Some(name) = type_is.place_name(self.db) {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str(" @ ")?;
|
f.write_str(" @ ")?;
|
||||||
f.write_str(&name)?;
|
f.write_str(&name)?;
|
||||||
}
|
}
|
||||||
|
|
@ -1029,6 +1057,7 @@ impl<'db> FmtDetailed<'db> for DisplayOverloadLiteral<'db> {
|
||||||
settings: self.settings.clone(),
|
settings: self.settings.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("def ")?;
|
f.write_str("def ")?;
|
||||||
write!(f, "{}", self.literal.name(self.db))?;
|
write!(f, "{}", self.literal.name(self.db))?;
|
||||||
type_parameters.fmt_detailed(f)?;
|
type_parameters.fmt_detailed(f)?;
|
||||||
|
|
@ -1075,7 +1104,7 @@ impl<'db> FmtDetailed<'db> for DisplayFunctionType<'db> {
|
||||||
db: self.db,
|
db: self.db,
|
||||||
settings: self.settings.clone(),
|
settings: self.settings.clone(),
|
||||||
};
|
};
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str("def ")?;
|
f.write_str("def ")?;
|
||||||
write!(f, "{}", self.ty.name(self.db))?;
|
write!(f, "{}", self.ty.name(self.db))?;
|
||||||
type_parameters.fmt_detailed(f)?;
|
type_parameters.fmt_detailed(f)?;
|
||||||
|
|
@ -1256,6 +1285,7 @@ impl<'db> DisplayGenericContext<'_, 'db> {
|
||||||
if idx > 0 {
|
if idx > 0 {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
|
f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
|
||||||
}
|
}
|
||||||
f.write_char(']')
|
f.write_char(']')
|
||||||
|
|
@ -1268,6 +1298,7 @@ impl<'db> DisplayGenericContext<'_, 'db> {
|
||||||
if idx > 0 {
|
if idx > 0 {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
|
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
|
||||||
}
|
}
|
||||||
f.write_char(']')
|
f.write_char(']')
|
||||||
|
|
@ -1358,6 +1389,7 @@ impl<'db> DisplaySpecialization<'db> {
|
||||||
if idx > 0 {
|
if idx > 0 {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
|
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
|
||||||
f.write_str(" = ")?;
|
f.write_str(" = ")?;
|
||||||
ty.display_with(self.db, self.settings.clone())
|
ty.display_with(self.db, self.settings.clone())
|
||||||
|
|
@ -1505,6 +1537,7 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> {
|
||||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||||
// Immediately write a marker signaling we're starting a signature
|
// Immediately write a marker signaling we're starting a signature
|
||||||
let _ = f.with_detail(TypeDetail::SignatureStart);
|
let _ = f.with_detail(TypeDetail::SignatureStart);
|
||||||
|
f.set_invalid_syntax();
|
||||||
// When we exit this function, write a marker signaling we're ending a signature
|
// When we exit this function, write a marker signaling we're ending a signature
|
||||||
let mut f = f.with_detail(TypeDetail::SignatureEnd);
|
let mut f = f.with_detail(TypeDetail::SignatureEnd);
|
||||||
let multiline = self.settings.multiline && self.parameters.len() > 1;
|
let multiline = self.settings.multiline && self.parameters.len() > 1;
|
||||||
|
|
@ -1694,6 +1727,7 @@ impl<'db> FmtDetailed<'db> for DisplayOmitted {
|
||||||
} else {
|
} else {
|
||||||
self.plural
|
self.plural
|
||||||
};
|
};
|
||||||
|
f.set_invalid_syntax();
|
||||||
write!(f, "... omitted {} {}", self.count, noun)
|
write!(f, "... omitted {} {}", self.count, noun)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1908,6 +1942,7 @@ impl<'db> FmtDetailed<'db> for DisplayIntersectionType<'_, 'db> {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.join(" & ").entries(tys).finish()
|
f.join(" & ").entries(tys).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1960,6 +1995,7 @@ struct DisplayMaybeParenthesizedType<'db> {
|
||||||
impl<'db> FmtDetailed<'db> for DisplayMaybeParenthesizedType<'db> {
|
impl<'db> FmtDetailed<'db> for DisplayMaybeParenthesizedType<'db> {
|
||||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||||
let write_parentheses = |f: &mut TypeWriter<'_, '_, 'db>| {
|
let write_parentheses = |f: &mut TypeWriter<'_, '_, 'db>| {
|
||||||
|
f.set_invalid_syntax();
|
||||||
f.write_char('(')?;
|
f.write_char('(')?;
|
||||||
self.ty
|
self.ty
|
||||||
.display_with(self.db, self.settings.clone())
|
.display_with(self.db, self.settings.clone())
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use lsp_types::request::InlayHintRequest;
|
use lsp_types::request::InlayHintRequest;
|
||||||
use lsp_types::{InlayHintParams, Url};
|
use lsp_types::{InlayHintParams, Url};
|
||||||
use ty_ide::{InlayHintKind, InlayHintLabel, inlay_hints};
|
use ruff_db::files::File;
|
||||||
|
use ty_ide::{InlayHintKind, InlayHintLabel, InlayHintTextEdit, inlay_hints};
|
||||||
use ty_project::ProjectDatabase;
|
use ty_project::ProjectDatabase;
|
||||||
|
|
||||||
use crate::PositionEncoding;
|
use crate::PositionEncoding;
|
||||||
|
|
@ -64,7 +65,14 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
||||||
padding_left: None,
|
padding_left: None,
|
||||||
padding_right: None,
|
padding_right: None,
|
||||||
data: None,
|
data: None,
|
||||||
text_edits: None,
|
text_edits: Some(
|
||||||
|
hint.text_edits
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|text_edit| {
|
||||||
|
inlay_hint_text_edit(text_edit, db, file, snapshot.encoding())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -100,3 +108,26 @@ fn inlay_hint_label(
|
||||||
}
|
}
|
||||||
lsp_types::InlayHintLabel::LabelParts(label_parts)
|
lsp_types::InlayHintLabel::LabelParts(label_parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inlay_hint_text_edit(
|
||||||
|
inlay_hint_text_edit: InlayHintTextEdit,
|
||||||
|
db: &ProjectDatabase,
|
||||||
|
file: File,
|
||||||
|
encoding: PositionEncoding,
|
||||||
|
) -> Option<lsp_types::TextEdit> {
|
||||||
|
Some(lsp_types::TextEdit {
|
||||||
|
range: lsp_types::Range {
|
||||||
|
start: inlay_hint_text_edit
|
||||||
|
.range
|
||||||
|
.start()
|
||||||
|
.to_lsp_position(db, file, encoding)?
|
||||||
|
.local_position(),
|
||||||
|
end: inlay_hint_text_edit
|
||||||
|
.range
|
||||||
|
.end()
|
||||||
|
.to_lsp_position(db, file, encoding)?
|
||||||
|
.local_position(),
|
||||||
|
},
|
||||||
|
new_text: inlay_hint_text_edit.new_text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,22 @@ y = foo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"kind": 1
|
"kind": 1,
|
||||||
|
"textEdits": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newText": ": int"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": {
|
"position": {
|
||||||
|
|
@ -91,7 +106,8 @@ y = foo(1)
|
||||||
"value": "="
|
"value": "="
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"kind": 2
|
"kind": 2,
|
||||||
|
"textEdits": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
"#);
|
"#);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue