| //! 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}; |
| |
| 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::Type, |
| label, |
| }); |
| |
| 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, |
| 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, 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 }, |
| 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: Chaining, |
| label: [ |
| "impl ", |
| InlayHintLabelPart { |
| text: "Iterator", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 1, |
| ), |
| range: 2611..2619, |
| }, |
| ), |
| tooltip: "", |
| }, |
| "<", |
| InlayHintLabelPart { |
| text: "Item", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 1, |
| ), |
| range: 2643..2647, |
| }, |
| ), |
| tooltip: "", |
| }, |
| " = impl ", |
| InlayHintLabelPart { |
| text: "Iterator", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 1, |
| ), |
| range: 2611..2619, |
| }, |
| ), |
| tooltip: "", |
| }, |
| "<", |
| InlayHintLabelPart { |
| text: "Item", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 1, |
| ), |
| range: 2643..2647, |
| }, |
| ), |
| tooltip: "", |
| }, |
| " = &&str>>", |
| ], |
| }, |
| InlayHint { |
| range: 484..485, |
| kind: Chaining, |
| label: [ |
| "", |
| InlayHintLabelPart { |
| text: "SliceIter", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 0, |
| ), |
| range: 289..298, |
| }, |
| ), |
| tooltip: "", |
| }, |
| "<", |
| InlayHintLabelPart { |
| text: "Container", |
| linked_location: Some( |
| FileRange { |
| file_id: FileId( |
| 0, |
| ), |
| range: 238..247, |
| }, |
| ), |
| tooltip: "", |
| }, |
| ">", |
| ], |
| }, |
| ] |
| "#]], |
| ); |
| } |
| |
| #[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 const_pats_have_no_type_hints() { |
| check_types( |
| r#" |
| const FOO: usize = 0; |
| |
| fn main() { |
| match 0 { |
| FOO => (), |
| _ => () |
| } |
| }"#, |
| ); |
| } |
| |
| #[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<…>> |
| }"#, |
| ); |
| } |
| } |