+use std::{collections::HashSet, ops::ControlFlow};
+
use either::Either;
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
use ide_db::{
base_db::{FileRange, SourceDatabase},
- defs::{Definition, NameClass, NameRefClass},
+ defs::Definition,
helpers::{
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
- pick_best_token, try_resolve_derive_input_at, FamousDefs,
+ pick_best_token, FamousDefs,
},
RootDatabase,
};
use itertools::Itertools;
use stdx::format_to;
use syntax::{
- algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
- SyntaxKind::*, SyntaxNode, SyntaxToken, T,
+ algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
+ SyntaxNode, SyntaxToken, TextRange, TextSize, T,
};
use crate::{
}
let offset = range.start();
- let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
})?;
- let token = sema.descend_into_macros(token);
- let mut range_override = None;
- let node = token.parent()?;
- let definition = match_ast! {
- match node {
- ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
- NameClass::Definition(it) | NameClass::ConstReference(it) => it,
- NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
- }),
- ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|class| match class {
- NameRefClass::Definition(def) => def,
- NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
- Definition::Field(field_ref)
- }
- }),
- ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
- || {
- NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
- NameRefClass::Definition(it) => Some(it),
- _ => None,
- })
- },
- NameClass::defined,
- ),
- _ => {
- // intra-doc links
- if ast::Comment::cast(token.clone()).is_some() {
- cov_mark::hit!(no_highlight_on_comment_hover);
- let (attributes, def) = doc_attributes(&sema, &node)?;
- let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
- let (idl_range, link, ns) =
- extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
- let mapped = doc_mapping.map(range)?;
- (mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
- })?;
- range_override = Some(idl_range);
- Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
- Either::Left(it) => Definition::ModuleDef(it),
- Either::Right(it) => Definition::Macro(it),
- })
- // attributes, require special machinery as they are mere ident tokens
- } else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
- // lints
- if let res@Some(_) = try_hover_for_lint(&attr, &token) {
- return res;
- // derives
- } else {
- range_override = Some(token.text_range());
- try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
- }
- } else {
- None
- }
- },
- }
- };
+ let mut seen = HashSet::default();
- if let Some(definition) = definition {
- let famous_defs = match &definition {
- Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
- Some(FamousDefs(&sema, sema.scope(&node).krate()))
- }
- _ => None,
- };
- if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
- let mut res = HoverResult::default();
- res.markup = process_markup(sema.db, definition, &markup, config);
- if let Some(action) = show_implementations_action(db, definition) {
- res.actions.push(action);
- }
+ let mut fallback = None;
+ // attributes, require special machinery as they are mere ident tokens
- if let Some(action) = show_fn_references_action(db, definition) {
- res.actions.push(action);
- }
+ let descend_macros = sema.descend_into_macros_many(original_token.clone());
- if let Some(action) = runnable_action(&sema, definition, file_id) {
- res.actions.push(action);
+ for token in &descend_macros {
+ if token.kind() != COMMENT {
+ if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
+ // lints
+ if let Some(res) = try_hover_for_lint(&attr, &token) {
+ return Some(res);
+ }
}
+ }
+ }
- if let Some(action) = goto_type_action_for_def(db, definition) {
- res.actions.push(action);
+ descend_macros
+ .iter()
+ .filter_map(|token| match token.parent() {
+ Some(node) => {
+ match find_hover_result(
+ &sema,
+ file_id,
+ offset,
+ config,
+ &original_token,
+ token,
+ &node,
+ &mut seen,
+ ) {
+ Some(res) => match res {
+ ControlFlow::Break(inner) => Some(inner),
+ ControlFlow::Continue(_) => {
+ if fallback.is_none() {
+ // FIXME we're only taking the first fallback into account that's not `None`
+ fallback = hover_for_keyword(&sema, config, &token)
+ .or(type_hover(&sema, config, &token));
+ }
+ None
+ }
+ },
+ None => None,
+ }
}
+ None => None,
+ })
+ // reduce all descends into a single `RangeInfo`
+ // that spans from the earliest start to the latest end (fishy/FIXME),
+ // concatenates all `Markup`s with `\n---\n`,
+ // and accumulates all actions into its `actions` vector.
+ .reduce(|mut acc, RangeInfo { range, mut info }| {
+ let start = acc.range.start().min(range.start());
+ let end = acc.range.end().max(range.end());
+
+ acc.range = TextRange::new(start, end);
+ acc.info.actions.append(&mut info.actions);
+ acc.info.markup = Markup::from(format!("{}\n---\n{}", acc.info.markup, info.markup));
+ acc
+ })
+ .or(fallback)
+}
+
+fn find_hover_result(
+ sema: &Semantics<RootDatabase>,
+ file_id: FileId,
+ offset: TextSize,
+ config: &HoverConfig,
+ original_token: &SyntaxToken,
+ token: &SyntaxToken,
+ node: &SyntaxNode,
+ seen: &mut HashSet<Definition>,
+) -> Option<ControlFlow<RangeInfo<HoverResult>>> {
+ let mut range_override = None;
- let range = range_override.unwrap_or_else(|| sema.original_range(&node).range);
- return Some(RangeInfo::new(range, res));
+ // intra-doc links and attributes are special cased
+ // so don't add them to the `seen` duplicate check
+ let mut add_to_seen_definitions = true;
+
+ let definition = Definition::from_node(sema, token).into_iter().next().or_else(|| {
+ // intra-doc links
+ // FIXME: move comment + attribute special cases somewhere else to simplify control flow,
+ // hopefully simplifying the return type of this function in the process
+ // (the `Break`/`Continue` distinction is needed to decide whether to use fallback hovers)
+ //
+ // FIXME: hovering the intra doc link to `Foo` not working:
+ //
+ // #[identity]
+ // trait Foo {
+ // /// [`Foo`]
+ // fn foo() {}
+ if token.kind() == COMMENT {
+ add_to_seen_definitions = false;
+ cov_mark::hit!(no_highlight_on_comment_hover);
+ let (attributes, def) = doc_attributes(sema, node)?;
+ let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
+ let (idl_range, link, ns) = extract_definitions_from_docs(&docs).into_iter().find_map(
+ |(range, link, ns)| {
+ let mapped = doc_mapping.map(range)?;
+ (mapped.file_id == file_id.into() && mapped.value.contains(offset))
+ .then(|| (mapped.value, link, ns))
+ },
+ )?;
+ range_override = Some(idl_range);
+ Some(match resolve_doc_path_for_def(sema.db, def, &link, ns)? {
+ Either::Left(it) => Definition::ModuleDef(it),
+ Either::Right(it) => Definition::Macro(it),
+ })
+ } else {
+ None
}
- }
+ });
- if let res @ Some(_) = hover_for_keyword(&sema, config, &token) {
- return res;
+ if let Some(definition) = definition {
+ // skip duplicates
+ if seen.contains(&definition) {
+ return None;
+ }
+ if add_to_seen_definitions {
+ seen.insert(definition);
+ }
+ if let Some(res) = hover_for_definition(sema, file_id, definition, &node, config) {
+ let range = range_override.unwrap_or_else(|| original_token.text_range());
+ return Some(ControlFlow::Break(RangeInfo::new(range, res)));
+ }
}
- // No definition below cursor, fall back to showing type hovers.
+ Some(ControlFlow::Continue(()))
+}
+fn type_hover(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
+ if token.kind() == COMMENT {
+ return None;
+ }
let node = token
.ancestors()
.take_while(|it| !ast::Item::can_cast(it.kind()))
match node {
ast::Expr(it) => Either::Left(it),
ast::Pat(it) => Either::Right(it),
- // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
+ // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
// (e.g expanding a builtin macro). So we give up here.
ast::MacroCall(_it) => return None,
_ => return None,
sema: &Semantics<RootDatabase>,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
- let expr = file.covering_element(range).ancestors().find_map(|it| {
+ let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
match_ast! {
match it {
ast::Expr(expr) => Some(Either::Left(expr)),
}
}
})?;
- hover_type_info(sema, config, &expr).map(|it| {
- let range = match expr {
+ let res = match &expr_or_pat {
+ Either::Left(ast::Expr::TryExpr(try_expr)) => hover_try_expr(sema, config, try_expr),
+ Either::Left(ast::Expr::PrefixExpr(prefix_expr))
+ if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ hover_deref_expr(sema, config, prefix_expr)
+ }
+ _ => None,
+ };
+ let res = res.or_else(|| hover_type_info(sema, config, &expr_or_pat));
+ res.map(|it| {
+ let range = match expr_or_pat {
Either::Left(it) => it.syntax().text_range(),
Either::Right(it) => it.syntax().text_range(),
};
})
}
+fn hover_try_expr(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ try_expr: &ast::TryExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
+ let mut ancestors = try_expr.syntax().ancestors();
+ let mut body_ty = loop {
+ let next = ancestors.next()?;
+ break match_ast! {
+ match next {
+ ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
+ ast::Item(__) => return None,
+ ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
+ ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
+ sema.type_of_expr(&effect.block_expr()?.into())?.original
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ };
+
+ if inner_ty == body_ty {
+ return None;
+ }
+
+ let mut inner_ty = inner_ty;
+ let mut s = "Try Target".to_owned();
+
+ let adts = inner_ty.as_adt().zip(body_ty.as_adt());
+ if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
+ let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate());
+ // special case for two options, there is no value in showing them
+ if let Some(option_enum) = famous_defs.core_option_Option() {
+ if inner == option_enum && body == option_enum {
+ cov_mark::hit!(hover_try_expr_opt_opt);
+ return None;
+ }
+ }
+
+ // special case two results to show the error variants only
+ if let Some(result_enum) = famous_defs.core_result_Result() {
+ if inner == result_enum && body == result_enum {
+ let error_type_args =
+ inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
+ if let Some((inner, body)) = error_type_args {
+ inner_ty = inner;
+ body_ty = body;
+ s = "Try Error".to_owned();
+ }
+ }
+ }
+ }
+
+ let mut res = HoverResult::default();
+
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ let inner_ty = inner_ty.display(sema.db).to_string();
+ let body_ty = body_ty.display(sema.db).to_string();
+ let ty_len_max = inner_ty.len().max(body_ty.len());
+
+ let l = "Propagated as: ".len() - " Type: ".len();
+ let static_text_len_diff = l as isize - s.len() as isize;
+ let tpad = static_text_len_diff.max(0) as usize;
+ let ppad = static_text_len_diff.min(0).abs() as usize;
+
+ res.markup = format!(
+ "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ s,
+ inner_ty,
+ body_ty,
+ pad0 = ty_len_max + tpad,
+ pad1 = ty_len_max + ppad,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into();
+ Some(res)
+}
+
+fn hover_deref_expr(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ deref_expr: &ast::PrefixExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
+ let TypeInfo { original, adjusted } =
+ sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let coerced_len = "Coerced to: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len)
+ .max(adjusted.len() + coerced_len)
+ .max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ inner,
+ original,
+ adjusted,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ opad = max_len - coerced_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ let original = original.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len).max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ inner,
+ original,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ Some(res)
+}
+
fn hover_type_info(
sema: &Semantics<RootDatabase>,
config: &HoverConfig,
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
let original = original.display(sema.db).to_string();
let adjusted = adjusted_ty.display(sema.db).to_string();
+ let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
format!(
- "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
- uncoerced = original,
- coerced = adjusted,
- // 6 base padding for difference of length of the two text prefixes
- apad = 6 + adjusted.len().max(original.len()),
+ "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
opad = original.len(),
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
} else {
def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
}
-fn hover_for_definition(
+pub(crate) fn hover_for_definition(
+ sema: &Semantics<RootDatabase>,
+ file_id: FileId,
+ definition: Definition,
+ node: &SyntaxNode,
+ config: &HoverConfig,
+) -> Option<HoverResult> {
+ let famous_defs = match &definition {
+ Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
+ Some(FamousDefs(&sema, sema.scope(&node).krate()))
+ }
+ _ => None,
+ };
+ if let Some(markup) = markup_for_definition(sema.db, definition, famous_defs.as_ref(), config) {
+ let mut res = HoverResult::default();
+ res.markup = process_markup(sema.db, definition, &markup, config);
+ if let Some(action) = show_implementations_action(sema.db, definition) {
+ res.actions.push(action);
+ }
+
+ if let Some(action) = show_fn_references_action(sema.db, definition) {
+ res.actions.push(action);
+ }
+
+ if let Some(action) = runnable_action(&sema, definition, file_id) {
+ res.actions.push(action);
+ }
+
+ if let Some(action) = goto_type_action_for_def(sema.db, definition) {
+ res.actions.push(action);
+ }
+ return Some(res);
+ }
+ None
+}
+
+fn markup_for_definition(
db: &RootDatabase,
def: Definition,
famous_defs: Option<&FamousDefs>,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap();
- assert!(hover.is_none());
+ assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
}
fn check(ra_fixture: &str, expect: Expect) {
assert!(hover.is_none());
}
+ #[test]
+ fn hover_descend_macros_avoids_duplicates() {
+ check(
+ r#"
+macro_rules! dupe_use {
+ ($local:ident) => {
+ {
+ $local;
+ $local;
+ }
+ }
+}
+fn foo() {
+ let local = 0;
+ dupe_use!(local$0);
+}
+"#,
+ expect![[r#"
+ *local*
+
+ ```rust
+ let local: i32
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hover_shows_all_macro_descends() {
+ check(
+ r#"
+macro_rules! m {
+ ($name:ident) => {
+ /// Outer
+ fn $name() {}
+
+ mod module {
+ /// Inner
+ fn $name() {}
+ }
+ };
+}
+
+m!(ab$0c);
+ "#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ test::module
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Inner
+ ---
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Outer
+ "#]],
+ );
+ }
+
#[test]
fn hover_shows_type_of_an_expression() {
check(
```
"#]],
);
+
+ // Use literal `crate` in path
+ check(
+ r#"
+pub struct X;
+
+fn foo() -> crate::X { X }
+
+fn main() { f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo() -> crate::X
+ ```
+ "#]],
+ );
+
+ // Check `super` in path
+ check(
+ r#"
+pub struct X;
+
+mod m { pub fn foo() -> super::X { super::X } }
+
+fn main() { m::f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub fn foo() -> super::X
+ ```
+ "#]],
+ );
}
#[test]
);
}
+ #[test]
+ fn test_hover_through_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+ }
+
#[test]
fn test_hover_through_expr_in_macro() {
check(
r#"
//- minicore: sized
struct Foo<T>(T);
-trait Copy {}
-trait Clone {}
-impl<T: Copy + Clone> Foo<T$0> where T: Sized {}
+trait TraitA {}
+trait TraitB {}
+impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
"#,
expect![[r#"
*T*
```rust
- T: Copy + Clone
+ T: TraitA + TraitB
```
"#]],
);
check(
r#"
+//- minicore: sized
struct Foo<T>(T);
impl<T> Foo<T$0> {}
"#,
// lifetimes bounds arent being tracked yet
check(
r#"
+//- minicore: sized
struct Foo<T>(T);
impl<T: 'static> Foo<T$0> {}
"#,
}
#[test]
- fn hover_type_param_not_sized() {
+ fn hover_type_param_sized_bounds() {
+ // implicit `: Sized` bound
check(
r#"
//- minicore: sized
+trait Trait {}
struct Foo<T>(T);
-trait Copy {}
-trait Clone {}
-impl<T: Copy + Clone> Foo<T$0> where T: ?Sized {}
+impl<T: Trait> Foo<T$0> {}
"#,
expect![[r#"
*T*
```rust
- T: Copy + Clone + ?Sized
+ T: Trait
```
"#]],
);
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait + ?Sized> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ mod type_param_sized_bounds {
+ use super::*;
+
+ #[test]
+ fn single_implicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_explicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_implicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_explicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn mixed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized + Sized + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
}
#[test]
);
}
+ #[test]
+ fn hover_attribute_in_macro() {
+ check(
+ r#"
+macro_rules! identity {
+ ($struct:item) => {
+ $struct
+ };
+}
+#[rustc_builtin_macro]
+pub macro Copy {}
+identity!{
+ #[derive(Copy$0)]
+ struct Foo;
+}
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub macro Copy
+ ```
+ "#]],
+ );
+ }
+
#[test]
fn hover_derive_input() {
check(
"#]],
);
}
+
+ #[test]
+ fn hover_try_expr_res() {
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), FooError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+struct BarError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), BarError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Error Type: BarError
+ Propagated as: FooError
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hover_try_expr() {
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Looooong> {
+ $0NotResult((), Short)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Short>
+ Propagated as: NotResult<(), Looooong>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Short> {
+ $0NotResult((), Looooong)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Looooong>
+ Propagated as: NotResult<(), Short>
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hover_try_expr_option() {
+ cov_mark::check!(hover_try_expr_opt_opt);
+ check_hover_range(
+ r#"
+//- minicore: option, try
+
+fn foo() -> Option<()> {
+ $0Some(0)?$0;
+ None
+}
+"#,
+ expect![[r#"
+ ```rust
+ <Option<i32> as Try>::Output
+ ```"#]],
+ );
+ }
+
+ #[test]
+ fn hover_deref_expr() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: 0 };
+ let y: i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<i32>
+ To type: i32
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hover_deref_expr_with_coercion() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: &&&&&0 };
+ let y: &i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<&&&&&i32>
+ To type: &&&&&i32
+ Coerced to: &i32
+ ```
+ "#]],
+ );
+ }
}