Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints/bind_pat.rs')
| -rw-r--r-- | crates/ide/src/inlay_hints/bind_pat.rs | 450 |
1 files changed, 314 insertions, 136 deletions
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs index 6a50927333..07b9f9cc1f 100644 --- a/crates/ide/src/inlay_hints/bind_pat.rs +++ b/crates/ide/src/inlay_hints/bind_pat.rs @@ -3,7 +3,7 @@ //! fn f(a: i32, b: i32) -> i32 { a + b } //! let _x /* i32 */= f(4, 4); //! ``` -use hir::{Semantics, TypeInfo}; +use hir::Semantics; use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase}; use itertools::Itertools; @@ -12,9 +12,10 @@ use syntax::{ match_ast, }; -use crate::{inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind}; - -use super::label_of_ty; +use crate::{ + inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit}, + InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, +}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -27,15 +28,45 @@ pub(super) fn hints( return None; } + let parent = pat.syntax().parent()?; + let type_ascriptable = match_ast! { + match parent { + ast::Param(it) => { + if it.ty().is_some() { + return None; + } + Some(it.colon_token()) + }, + ast::LetStmt(it) => { + if config.hide_closure_initialization_hints { + if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() { + if closure_has_block_body(&closure) { + return None; + } + } + } + if it.ty().is_some() { + return None; + } + Some(it.colon_token()) + }, + _ => None + } + }; + let descended = sema.descend_node_into_attributes(pat.clone()).pop(); let desc_pat = descended.as_ref().unwrap_or(pat); - let ty = sema.type_of_pat(&desc_pat.clone().into())?.original; + let ty = sema.type_of_binding_in_pat(desc_pat)?; - if should_not_display_type_hint(sema, config, pat, &ty) { + if ty.is_unknown() { return None; } - let label = label_of_ty(famous_defs, config, ty)?; + if sema.resolve_bind_pat_to_const(pat).is_some() { + return None; + } + + let mut label = label_of_ty(famous_defs, config, &ty)?; if config.hide_named_constructor_hints && is_named_constructor(sema, pat, &label.to_string()).is_some() @@ -43,69 +74,46 @@ pub(super) fn hints( return None; } + let text_edit = if let Some(colon_token) = &type_ascriptable { + ty_to_text_edit( + sema, + desc_pat.syntax(), + &ty, + colon_token + .as_ref() + .map_or_else(|| pat.syntax().text_range(), |t| t.text_range()) + .end(), + if colon_token.is_some() { String::new() } else { String::from(": ") }, + ) + } else { + None + }; + + let render_colons = config.render_colons && !matches!(type_ascriptable, Some(Some(_))); + if render_colons { + label.prepend_str(": "); + } + + let text_range = match pat.name() { + Some(name) => name.syntax().text_range(), + None => pat.syntax().text_range(), + }; acc.push(InlayHint { - range: match pat.name() { - Some(name) => name.syntax().text_range(), - None => pat.syntax().text_range(), + range: match type_ascriptable { + Some(Some(t)) => text_range.cover(t.text_range()), + _ => text_range, }, kind: InlayKind::Type, label, + text_edit, + position: InlayHintPosition::After, + pad_left: !render_colons, + pad_right: false, }); Some(()) } -fn should_not_display_type_hint( - sema: &Semantics<'_, RootDatabase>, - config: &InlayHintsConfig, - bind_pat: &ast::IdentPat, - pat_ty: &hir::Type, -) -> bool { - let db = sema.db; - - if pat_ty.is_unknown() { - return true; - } - - if sema.resolve_bind_pat_to_const(bind_pat).is_some() { - return true; - } - - for node in bind_pat.syntax().ancestors() { - match_ast! { - match node { - ast::LetStmt(it) => { - if config.hide_closure_initialization_hints { - if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() { - if closure_has_block_body(&closure) { - return true; - } - } - } - return it.ty().is_some() - }, - // FIXME: We might wanna show type hints in parameters for non-top level patterns as well - ast::Param(it) => return it.ty().is_some(), - ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty), - ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty), - ast::IfExpr(_) => return false, - ast::WhileExpr(_) => return false, - ast::ForExpr(it) => { - // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit). - // Type of expr should be iterable. - return it.in_token().is_none() || - it.iterable() - .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr)) - .map(TypeInfo::original) - .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit()) - }, - _ => (), - } - } - } - false -} - fn is_named_constructor( sema: &Semantics<'_, RootDatabase>, pat: &ast::IdentPat, @@ -159,30 +167,20 @@ fn is_named_constructor( (ctor_name == ty_name).then_some(()) } -fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool { - if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() { - let pat_text = bind_pat.to_string(); - enum_data - .variants(db) - .into_iter() - .map(|variant| variant.name(db).to_smol_str()) - .any(|enum_name| enum_name == pat_text) - } else { - false - } -} - #[cfg(test)] mod tests { // This module also contains tests for super::closure_ret + use expect_test::expect; + use hir::ClosureStyle; use syntax::{TextRange, TextSize}; use test_utils::extract_annotations; - use crate::{fixture, inlay_hints::InlayHintsConfig}; + use crate::{fixture, inlay_hints::InlayHintsConfig, ClosureReturnTypeHints}; - use crate::inlay_hints::tests::{check, check_with_config, DISABLED_CONFIG, TEST_CONFIG}; - use crate::ClosureReturnTypeHints; + use crate::inlay_hints::tests::{ + check, check_edit, check_no_edit, check_with_config, DISABLED_CONFIG, TEST_CONFIG, + }; #[track_caller] fn check_types(ra_fixture: &str) { @@ -235,7 +233,7 @@ fn main() { let zz_ref = &zz; //^^^^^^ &Test<i32> let test = || zz; - //^^^^ || -> Test<i32> + //^^^^ impl FnOnce() -> Test<i32> }"#, ); } @@ -528,24 +526,7 @@ fn main() { struct Test { a: Option<u32>, b: u8 } fn main() { - let test = Some(Test { a: Some(3), b: 1 }); - //^^^^ Option<Test> - if let None = &test {}; - if let test = &test {}; - //^^^^ &Option<Test> - if let Some(test) = &test {}; - //^^^^ &Test - if let Some(Test { a, b }) = &test {}; - //^ &Option<u32> ^ &u8 - if let Some(Test { a: x, b: y }) = &test {}; - //^ &Option<u32> ^ &u8 - if let Some(Test { a: Some(x), b: y }) = &test {}; - //^ &u32 ^ &u8 - if let Some(Test { a: None, b: y }) = &test {}; - //^ &u8 - if let Some(Test { b: y, .. }) = &test {}; - //^ &u8 - if test == None {} + }"#, ); } @@ -560,8 +541,8 @@ struct Test { a: Option<u32>, b: u8 } fn main() { let test = Some(Test { a: Some(3), b: 1 }); //^^^^ Option<Test> - while let Some(Test { a: Some(x), b: y }) = &test {}; - //^ &u32 ^ &u8 + while let Some(Test { a: Some(x), b: y }) = &test {}; + //^ &u32 ^ &u8 }"#, ); } @@ -753,7 +734,7 @@ fn main() { let func = times2; // ^^^^ fn times2(i32) -> i32 let closure = |x: i32| x * 2; - // ^^^^^^^ |i32| -> i32 + // ^^^^^^^ impl Fn(i32) -> i32 } fn fallible() -> ControlFlow<()> { @@ -811,49 +792,90 @@ fn fallible() -> ControlFlow<()> { } #[test] - fn closures() { - check( + fn closure_style() { + check_with_config( + InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, r#" +//- minicore: fn fn main() { - let mut start = 0; - //^^^^^ i32 - (0..2).for_each(|increment | { start += increment; }); - //^^^^^^^^^ i32 - - let multiply = - //^^^^^^^^ |i32, i32| -> i32 - | a, b| a * b - //^ i32 ^ i32 - - ; - - let _: i32 = multiply(1, 2); - //^ a ^ b - let multiply_ref = &multiply; - //^^^^^^^^^^^^ &|i32, i32| -> i32 - - let return_42 = || 42; - //^^^^^^^^^ || -> i32 - || { 42 }; - //^^ i32 -}"#, + let x = || 2; + //^ impl Fn() -> i32 + let y = |t: i32| x() + t; + //^ impl Fn(i32) -> i32 + let mut t = 5; + //^ i32 + let z = |k: i32| { t += k; }; + //^ impl FnMut(i32) + let p = (y, z); + //^ (impl Fn(i32) -> i32, impl FnMut(i32)) +} + "#, ); - } - - #[test] - fn return_type_hints_for_closure_without_block() { check_with_config( InlayHintsConfig { - closure_return_type_hints: ClosureReturnTypeHints::Always, + type_hints: true, + closure_style: ClosureStyle::RANotation, ..DISABLED_CONFIG }, r#" +//- minicore: fn fn main() { - let a = || { 0 }; - //^^ i32 - let b = || 0; - //^^ i32 -}"#, + let x = || 2; + //^ || -> i32 + let y = |t: i32| x() + t; + //^ |i32| -> i32 + let mut t = 5; + //^ i32 + let z = |k: i32| { t += k; }; + //^ |i32| -> () + let p = (y, z); + //^ (|i32| -> i32, |i32| -> ()) +} + "#, + ); + check_with_config( + InlayHintsConfig { + type_hints: true, + closure_style: ClosureStyle::ClosureWithId, + ..DISABLED_CONFIG + }, + r#" +//- minicore: fn +fn main() { + let x = || 2; + //^ {closure#0} + let y = |t: i32| x() + t; + //^ {closure#1} + let mut t = 5; + //^ i32 + let z = |k: i32| { t += k; }; + //^ {closure#2} + let p = (y, z); + //^ ({closure#1}, {closure#2}) +} + "#, + ); + check_with_config( + InlayHintsConfig { + type_hints: true, + closure_style: ClosureStyle::Hide, + ..DISABLED_CONFIG + }, + r#" +//- minicore: fn +fn main() { + let x = || 2; + //^ … + let y = |t: i32| x() + t; + //^ … + let mut t = 5; + //^ i32 + let z = |k: i32| { t += k; }; + //^ … + let p = (y, z); + //^ (…, …) +} + "#, ); } @@ -871,13 +893,13 @@ fn main() { let multiple_2 = |x: i32| { x * 2 }; let multiple_2 = |x: i32| x * 2; - // ^^^^^^^^^^ |i32| -> i32 + // ^^^^^^^^^^ impl Fn(i32) -> i32 let (not) = (|x: bool| { !x }); - // ^^^ |bool| -> bool + // ^^^ impl Fn(bool) -> bool let (is_zero, _b) = (|x: usize| { x == 0 }, false); - // ^^^^^^^ |usize| -> bool + // ^^^^^^^ impl Fn(usize) -> bool // ^^ bool let plus_one = |x| { x + 1 }; @@ -923,4 +945,160 @@ fn main() { }"#, ); } + + #[test] + fn edit_for_let_stmt() { + check_edit( + TEST_CONFIG, + r#" +struct S<T>(T); +fn test<F>(v: S<(S<i32>, S<()>)>, f: F) { + let a = v; + let S((b, c)) = v; + let a @ S((b, c)) = v; + let a = f; +} +"#, + expect![[r#" + struct S<T>(T); + fn test<F>(v: S<(S<i32>, S<()>)>, f: F) { + let a: S<(S<i32>, S<()>)> = v; + let S((b, c)) = v; + let a @ S((b, c)): S<(S<i32>, S<()>)> = v; + let a: F = f; + } + "#]], + ); + } + + #[test] + fn edit_for_closure_param() { + check_edit( + TEST_CONFIG, + r#" +fn test<T>(t: T) { + let f = |a, b, c| {}; + let result = f(42, "", t); +} +"#, + expect![[r#" + fn test<T>(t: T) { + let f = |a: i32, b: &str, c: T| {}; + let result: () = f(42, "", t); + } + "#]], + ); + } + + #[test] + fn edit_for_closure_ret() { + check_edit( + TEST_CONFIG, + r#" +struct S<T>(T); +fn test() { + let f = || { 3 }; + let f = |a: S<usize>| { S(a) }; +} +"#, + expect![[r#" + struct S<T>(T); + fn test() { + let f = || -> i32 { 3 }; + let f = |a: S<usize>| -> S<S<usize>> { S(a) }; + } + "#]], + ); + } + + #[test] + fn edit_prefixes_paths() { + check_edit( + TEST_CONFIG, + r#" +pub struct S<T>(T); +mod middle { + pub struct S<T, U>(T, U); + pub fn make() -> S<inner::S<i64>, super::S<usize>> { loop {} } + + mod inner { + pub struct S<T>(T); + } + + fn test() { + let a = make(); + } +} +"#, + expect![[r#" + pub struct S<T>(T); + mod middle { + pub struct S<T, U>(T, U); + pub fn make() -> S<inner::S<i64>, super::S<usize>> { loop {} } + + mod inner { + pub struct S<T>(T); + } + + fn test() { + let a: S<inner::S<i64>, crate::S<usize>> = make(); + } + } + "#]], + ); + } + + #[test] + fn no_edit_for_top_pat_where_type_annotation_is_invalid() { + check_no_edit( + TEST_CONFIG, + r#" +fn test() { + if let a = 42 {} + while let a = 42 {} + match 42 { + a => (), + } +} +"#, + ) + } + + #[test] + fn no_edit_for_opaque_type() { + check_no_edit( + TEST_CONFIG, + r#" +trait Trait {} +struct S<T>(T); +fn foo() -> impl Trait {} +fn bar() -> S<impl Trait> {} +fn test() { + let a = foo(); + let a = bar(); + let f = || { foo() }; + let f = || { bar() }; +} +"#, + ); + } + + #[test] + fn no_edit_for_closure_return_without_body_block() { + // We can lift this limitation; see FIXME in closure_ret module. + let config = InlayHintsConfig { + closure_return_type_hints: ClosureReturnTypeHints::Always, + ..TEST_CONFIG + }; + check_no_edit( + config, + r#" +struct S<T>(T); +fn test() { + let f = || 3; + let f = |a: S<usize>| S(a); +} +"#, + ); + } } |