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 | 978 |
1 files changed, 978 insertions, 0 deletions
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs new file mode 100644 index 0000000000..adec19c765 --- /dev/null +++ b/crates/ide/src/inlay_hints/bind_pat.rs @@ -0,0 +1,978 @@ +//! Implementation of "type" inlay hints: +//! ```no_run +//! fn f(a: i32, b: i32) -> i32 { a + b } +//! let _x /* i32 */= f(4, 4); +//! ``` +use hir::{Semantics, TypeInfo}; +use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase}; + +use itertools::Itertools; +use syntax::{ + ast::{self, AstNode, HasName}, + match_ast, +}; + +use crate::{ + inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, +}; + +use super::label_of_ty; + +pub(super) fn hints( + acc: &mut Vec<InlayHint>, + famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig, + file_id: FileId, + pat: &ast::IdentPat, +) -> Option<()> { + if !config.type_hints { + return 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; + + if should_not_display_type_hint(sema, config, pat, &ty) { + return None; + } + + let label = label_of_ty(famous_defs, config, ty)?; + + if config.hide_named_constructor_hints + && is_named_constructor(sema, pat, &label.to_string()).is_some() + { + return None; + } + + acc.push(InlayHint { + range: match pat.name() { + Some(name) => name.syntax().text_range(), + None => pat.syntax().text_range(), + }, + kind: InlayKind::TypeHint, + label, + tooltip: pat + .name() + .map(|it| it.syntax().text_range()) + .map(|it| InlayTooltip::HoverRanged(file_id, it)), + }); + + 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 let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() { + if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() { + return true; + } + } + + if config.hide_closure_initialization_hints { + if let Some(parent) = bind_pat.syntax().parent() { + if let Some(it) = ast::LetStmt::cast(parent) { + if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() { + if closure_has_block_body(&closure) { + return true; + } + } + } + } + } + + for node in bind_pat.syntax().ancestors() { + match_ast! { + match node { + ast::LetStmt(it) => 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, + ty_name: &str, +) -> Option<()> { + let let_node = pat.syntax().parent()?; + let expr = match_ast! { + match let_node { + ast::LetStmt(it) => it.initializer(), + ast::LetExpr(it) => it.expr(), + _ => None, + } + }?; + + let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr); + // unwrap postfix expressions + let expr = match expr { + ast::Expr::TryExpr(it) => it.expr(), + ast::Expr::AwaitExpr(it) => it.expr(), + expr => Some(expr), + }?; + let expr = match expr { + ast::Expr::CallExpr(call) => match call.expr()? { + ast::Expr::PathExpr(path) => path, + _ => return None, + }, + ast::Expr::PathExpr(path) => path, + _ => return None, + }; + let path = expr.path()?; + + let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db); + let callable_kind = callable.map(|it| it.kind()); + let qual_seg = match callable_kind { + Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => { + path.qualifier()?.segment() + } + _ => path.segment(), + }?; + + let ctor_name = match qual_seg.kind()? { + ast::PathSegmentKind::Name(name_ref) => { + match qual_seg.generic_arg_list().map(|it| it.generic_args()) { + Some(generics) => format!("{name_ref}<{}>", generics.format(", ")), + None => name_ref.to_string(), + } + } + ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(), + _ => return None, + }; + (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 syntax::{TextRange, TextSize}; + use test_utils::extract_annotations; + + use crate::{fixture, inlay_hints::InlayHintsConfig}; + + use crate::inlay_hints::tests::{ + check, check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS, + TEST_CONFIG, + }; + use crate::ClosureReturnTypeHints; + + #[track_caller] + fn check_types(ra_fixture: &str) { + check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture); + } + + #[test] + fn type_hints_only() { + check_types( + r#" +fn foo(a: i32, b: i32) -> i32 { a + b } +fn main() { + let _x = foo(4, 4); + //^^ i32 +}"#, + ); + } + + #[test] + fn type_hints_bindings_after_at() { + check_types( + r#" +//- minicore: option +fn main() { + let ref foo @ bar @ ref mut baz = 0; + //^^^ &i32 + //^^^ i32 + //^^^ &mut i32 + let [x @ ..] = [0]; + //^ [i32; 1] + if let x @ Some(_) = Some(0) {} + //^ Option<i32> + let foo @ (bar, baz) = (3, 3); + //^^^ (i32, i32) + //^^^ i32 + //^^^ i32 +}"#, + ); + } + + #[test] + fn default_generic_types_should_not_be_displayed() { + check( + r#" +struct Test<K, T = u8> { k: K, t: T } + +fn main() { + let zz = Test { t: 23u8, k: 33 }; + //^^ Test<i32> + let zz_ref = &zz; + //^^^^^^ &Test<i32> + let test = || zz; + //^^^^ || -> Test<i32> +}"#, + ); + } + + #[test] + fn shorten_iterators_in_associated_params() { + check_types( + r#" +//- minicore: iterators +use core::iter; + +pub struct SomeIter<T> {} + +impl<T> SomeIter<T> { + pub fn new() -> Self { SomeIter {} } + pub fn push(&mut self, t: T) {} +} + +impl<T> Iterator for SomeIter<T> { + type Item = T; + fn next(&mut self) -> Option<Self::Item> { + None + } +} + +fn main() { + let mut some_iter = SomeIter::new(); + //^^^^^^^^^ SomeIter<Take<Repeat<i32>>> + some_iter.push(iter::repeat(2).take(2)); + let iter_of_iters = some_iter.take(2); + //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>> +} +"#, + ); + } + + #[test] + fn iterator_hint_regression_issue_12674() { + // Ensure we don't crash while solving the projection type of iterators. + check_expect( + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS }, + r#" +//- minicore: iterators +struct S<T>(T); +impl<T> S<T> { + fn iter(&self) -> Iter<'_, T> { loop {} } +} +struct Iter<'a, T: 'a>(&'a T); +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + fn next(&mut self) -> Option<Self::Item> { loop {} } +} +struct Container<'a> { + elements: S<&'a str>, +} +struct SliceIter<'a, T>(&'a T); +impl<'a, T> Iterator for SliceIter<'a, T> { + type Item = &'a T; + fn next(&mut self) -> Option<Self::Item> { loop {} } +} + +fn main(a: SliceIter<'_, Container>) { + a + .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v)))) + .map(|e| e); +} + "#, + expect![[r#" + [ + InlayHint { + range: 484..554, + kind: ChainingHint, + label: [ + "impl Iterator<Item = impl Iterator<Item = &&str>>", + ], + tooltip: Some( + HoverRanged( + FileId( + 0, + ), + 484..554, + ), + ), + }, + InlayHint { + range: 484..485, + kind: ChainingHint, + label: [ + "", + InlayHintLabelPart { + text: "SliceIter", + linked_location: Some( + FileRange { + file_id: FileId( + 0, + ), + range: 289..298, + }, + ), + }, + "<", + InlayHintLabelPart { + text: "Container", + linked_location: Some( + FileRange { + file_id: FileId( + 0, + ), + range: 238..247, + }, + ), + }, + ">", + ], + tooltip: Some( + HoverRanged( + FileId( + 0, + ), + 484..485, + ), + ), + }, + ] + "#]], + ); + } + + #[test] + fn infer_call_method_return_associated_types_with_generic() { + check_types( + r#" + pub trait Default { + fn default() -> Self; + } + pub trait Foo { + type Bar: Default; + } + + pub fn quux<T: Foo>() -> T::Bar { + let y = Default::default(); + //^ <T as Foo>::Bar + + y + } + "#, + ); + } + + #[test] + fn fn_hints() { + check_types( + r#" +//- minicore: fn, sized +fn foo() -> impl Fn() { loop {} } +fn foo1() -> impl Fn(f64) { loop {} } +fn foo2() -> impl Fn(f64, f64) { loop {} } +fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} } +fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} } +fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} } +fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} } +fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} } + +fn main() { + let foo = foo(); + // ^^^ impl Fn() + let foo = foo1(); + // ^^^ impl Fn(f64) + let foo = foo2(); + // ^^^ impl Fn(f64, f64) + let foo = foo3(); + // ^^^ impl Fn(f64, f64) -> u32 + let foo = foo4(); + // ^^^ &dyn Fn(f64, f64) -> u32 + let foo = foo5(); + // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32 + let foo = foo6(); + // ^^^ impl Fn(f64, f64) -> u32 + let foo = foo7(); + // ^^^ *const impl Fn(f64, f64) -> u32 +} +"#, + ) + } + + #[test] + fn check_hint_range_limit() { + let fixture = r#" + //- minicore: fn, sized + fn foo() -> impl Fn() { loop {} } + fn foo1() -> impl Fn(f64) { loop {} } + fn foo2() -> impl Fn(f64, f64) { loop {} } + fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} } + fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} } + fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} } + fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} } + fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} } + + fn main() { + let foo = foo(); + let foo = foo1(); + let foo = foo2(); + // ^^^ impl Fn(f64, f64) + let foo = foo3(); + // ^^^ impl Fn(f64, f64) -> u32 + let foo = foo4(); + let foo = foo5(); + let foo = foo6(); + let foo = foo7(); + } + "#; + let (analysis, file_id) = fixture::file(fixture); + let expected = extract_annotations(&analysis.file_text(file_id).unwrap()); + let inlay_hints = analysis + .inlay_hints( + &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, + file_id, + Some(TextRange::new(TextSize::from(500), TextSize::from(600))), + ) + .unwrap(); + let actual = + inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>(); + assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}"); + } + + #[test] + fn fn_hints_ptr_rpit_fn_parentheses() { + check_types( + r#" +//- minicore: fn, sized +trait Trait {} + +fn foo1() -> *const impl Fn() { loop {} } +fn foo2() -> *const (impl Fn() + Sized) { loop {} } +fn foo3() -> *const (impl Fn() + ?Sized) { loop {} } +fn foo4() -> *const (impl Sized + Fn()) { loop {} } +fn foo5() -> *const (impl ?Sized + Fn()) { loop {} } +fn foo6() -> *const (impl Fn() + Trait) { loop {} } +fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} } +fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} } +fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} } +fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} } + +fn main() { + let foo = foo1(); + // ^^^ *const impl Fn() + let foo = foo2(); + // ^^^ *const impl Fn() + let foo = foo3(); + // ^^^ *const (impl Fn() + ?Sized) + let foo = foo4(); + // ^^^ *const impl Fn() + let foo = foo5(); + // ^^^ *const (impl Fn() + ?Sized) + let foo = foo6(); + // ^^^ *const (impl Fn() + Trait) + let foo = foo7(); + // ^^^ *const (impl Fn() + Trait) + let foo = foo8(); + // ^^^ *const (impl Fn() + Trait + ?Sized) + let foo = foo9(); + // ^^^ *const (impl Fn() -> u8 + ?Sized) + let foo = foo10(); + // ^^^ *const impl Fn() +} +"#, + ) + } + + #[test] + fn unit_structs_have_no_type_hints() { + check_types( + r#" +//- minicore: result +struct SyntheticSyntax; + +fn main() { + match Ok(()) { + Ok(_) => (), + Err(SyntheticSyntax) => (), + } +}"#, + ); + } + + #[test] + fn let_statement() { + check_types( + r#" +#[derive(PartialEq)] +enum Option<T> { None, Some(T) } + +#[derive(PartialEq)] +struct Test { a: Option<u32>, b: u8 } + +fn main() { + struct InnerStruct {} + + let test = 54; + //^^^^ i32 + let test: i32 = 33; + let mut test = 33; + //^^^^ i32 + let _ = 22; + let test = "test"; + //^^^^ &str + let test = InnerStruct {}; + //^^^^ InnerStruct + + let test = unresolved(); + + let test = (42, 'a'); + //^^^^ (i32, char) + let (a, (b, (c,)) = (2, (3, (9.2,)); + //^ i32 ^ i32 ^ f64 + let &x = &92; + //^ i32 +}"#, + ); + } + + #[test] + fn if_expr() { + check_types( + r#" +//- minicore: option +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 {} +}"#, + ); + } + + #[test] + fn while_expr() { + check_types( + r#" +//- minicore: option +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 +}"#, + ); + } + + #[test] + fn match_arm_list() { + check_types( + r#" +//- minicore: option +struct Test { a: Option<u32>, b: u8 } + +fn main() { + match Some(Test { a: Some(3), b: 1 }) { + None => (), + test => (), + //^^^^ Option<Test> + Some(Test { a: Some(x), b: y }) => (), + //^ u32 ^ u8 + _ => {} + } +}"#, + ); + } + + #[test] + fn complete_for_hint() { + check_types( + r#" +//- minicore: iterator +pub struct Vec<T> {} + +impl<T> Vec<T> { + pub fn new() -> Self { Vec {} } + pub fn push(&mut self, t: T) {} +} + +impl<T> IntoIterator for Vec<T> { + type Item = T; + type IntoIter = IntoIter<T>; +} + +struct IntoIter<T> {} + +impl<T> Iterator for IntoIter<T> { + type Item = T; +} + +fn main() { + let mut data = Vec::new(); + //^^^^ Vec<&str> + data.push("foo"); + for i in data { + //^ &str + let z = i; + //^ &str + } +} +"#, + ); + } + + #[test] + fn multi_dyn_trait_bounds() { + check_types( + r#" +pub struct Vec<T> {} + +impl<T> Vec<T> { + pub fn new() -> Self { Vec {} } +} + +pub struct Box<T> {} + +trait Display {} +auto trait Sync {} + +fn main() { + // The block expression wrapping disables the constructor hint hiding logic + let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() }; + //^^ Vec<Box<&(dyn Display + Sync)>> + let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() }; + //^^ Vec<Box<*const (dyn Display + Sync)>> + let _v = { Vec::<Box<dyn Display + Sync>>::new() }; + //^^ Vec<Box<dyn Display + Sync>> +} +"#, + ); + } + + #[test] + fn shorten_iterator_hints() { + check_types( + r#" +//- minicore: iterators +use core::iter; + +struct MyIter; + +impl Iterator for MyIter { + type Item = (); + fn next(&mut self) -> Option<Self::Item> { + None + } +} + +fn main() { + let _x = MyIter; + //^^ MyIter + let _x = iter::repeat(0); + //^^ impl Iterator<Item = i32> + fn generic<T: Clone>(t: T) { + let _x = iter::repeat(t); + //^^ impl Iterator<Item = T> + let _chained = iter::repeat(t).take(10); + //^^^^^^^^ impl Iterator<Item = T> + } +} +"#, + ); + } + + #[test] + fn skip_constructor_and_enum_type_hints() { + check_with_config( + InlayHintsConfig { + type_hints: true, + hide_named_constructor_hints: true, + ..DISABLED_CONFIG + }, + r#" +//- minicore: try, option +use core::ops::ControlFlow; + +mod x { + pub mod y { pub struct Foo; } + pub struct Foo; + pub enum AnotherEnum { + Variant() + }; +} +struct Struct; +struct TupleStruct(); + +impl Struct { + fn new() -> Self { + Struct + } + fn try_new() -> ControlFlow<(), Self> { + ControlFlow::Continue(Struct) + } +} + +struct Generic<T>(T); +impl Generic<i32> { + fn new() -> Self { + Generic(0) + } +} + +enum Enum { + Variant(u32) +} + +fn times2(value: i32) -> i32 { + 2 * value +} + +fn main() { + let enumb = Enum::Variant(0); + + let strukt = x::Foo; + let strukt = x::y::Foo; + let strukt = Struct; + let strukt = Struct::new(); + + let tuple_struct = TupleStruct(); + + let generic0 = Generic::new(); + // ^^^^^^^^ Generic<i32> + let generic1 = Generic(0); + // ^^^^^^^^ Generic<i32> + let generic2 = Generic::<i32>::new(); + let generic3 = <Generic<i32>>::new(); + let generic4 = Generic::<i32>(0); + + + let option = Some(0); + // ^^^^^^ Option<i32> + let func = times2; + // ^^^^ fn times2(i32) -> i32 + let closure = |x: i32| x * 2; + // ^^^^^^^ |i32| -> i32 +} + +fn fallible() -> ControlFlow<()> { + let strukt = Struct::try_new()?; +} +"#, + ); + } + + #[test] + fn shows_constructor_type_hints_when_enabled() { + check_types( + r#" +//- minicore: try +use core::ops::ControlFlow; + +struct Struct; +struct TupleStruct(); + +impl Struct { + fn new() -> Self { + Struct + } + fn try_new() -> ControlFlow<(), Self> { + ControlFlow::Continue(Struct) + } +} + +struct Generic<T>(T); +impl Generic<i32> { + fn new() -> Self { + Generic(0) + } +} + +fn main() { + let strukt = Struct::new(); + // ^^^^^^ Struct + let tuple_struct = TupleStruct(); + // ^^^^^^^^^^^^ TupleStruct + let generic0 = Generic::new(); + // ^^^^^^^^ Generic<i32> + let generic1 = Generic::<i32>::new(); + // ^^^^^^^^ Generic<i32> + let generic2 = <Generic<i32>>::new(); + // ^^^^^^^^ Generic<i32> +} + +fn fallible() -> ControlFlow<()> { + let strukt = Struct::try_new()?; + // ^^^^^^ Struct +} +"#, + ); + } + + #[test] + fn closures() { + check( + r#" +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 +}"#, + ); + } + + #[test] + fn return_type_hints_for_closure_without_block() { + check_with_config( + InlayHintsConfig { + closure_return_type_hints: ClosureReturnTypeHints::Always, + ..DISABLED_CONFIG + }, + r#" +fn main() { + let a = || { 0 }; + //^^ i32 + let b = || 0; + //^^ i32 +}"#, + ); + } + + #[test] + fn skip_closure_type_hints() { + check_with_config( + InlayHintsConfig { + type_hints: true, + hide_closure_initialization_hints: true, + ..DISABLED_CONFIG + }, + r#" +//- minicore: fn +fn main() { + let multiple_2 = |x: i32| { x * 2 }; + + let multiple_2 = |x: i32| x * 2; + // ^^^^^^^^^^ |i32| -> i32 + + let (not) = (|x: bool| { !x }); + // ^^^ |bool| -> bool + + let (is_zero, _b) = (|x: usize| { x == 0 }, false); + // ^^^^^^^ |usize| -> bool + // ^^ bool + + let plus_one = |x| { x + 1 }; + // ^ u8 + foo(plus_one); + + let add_mul = bar(|x: u8| { x + 1 }); + // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized + + let closure = if let Some(6) = add_mul(2).checked_sub(1) { + // ^^^^^^^ fn(i32) -> i32 + |x: i32| { x * 2 } + } else { + |x: i32| { x * 3 } + }; +} + +fn foo(f: impl FnOnce(u8) -> u8) {} + +fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 { + move |x: u8| f(x) * 2 +} +"#, + ); + } + + #[test] + fn hint_truncation() { + check_with_config( + InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG }, + r#" +struct Smol<T>(T); + +struct VeryLongOuterName<T>(T); + +fn main() { + let a = Smol(0u32); + //^ Smol<u32> + let b = VeryLongOuterName(0usize); + //^ VeryLongOuterName<…> + let c = Smol(Smol(0u32)) + //^ Smol<Smol<…>> +}"#, + ); + } +} |