Merge pull request #19922 from Veykril/push-oxyomxrsplpx

feat: Add `dyn` keyword inlay hints
This commit is contained in:
Lukas Wirth 2025-06-04 09:51:22 +00:00 committed by GitHub
commit 2dc5aa885a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 405 additions and 217 deletions

View File

@ -931,11 +931,12 @@ pub fn new() {
// PATH_TYPE@23..26 // PATH_TYPE@23..26
// PATH@23..26 // PATH@23..26
// PATH_SEGMENT@23..26 // PATH_SEGMENT@23..26
// L_ANGLE@23..24 "<" // TYPE_ANCHOR@23..26
// PAREN_TYPE@24..26 // L_ANGLE@23..24 "<"
// L_PAREN@24..25 "(" // PAREN_TYPE@24..26
// ERROR@25..26 // L_PAREN@24..25 "("
// INT_NUMBER@25..26 "8" // ERROR@25..26
// INT_NUMBER@25..26 "8"
// PLUS@26..27 "+" // PLUS@26..27 "+"
// CONST_ARG@27..28 // CONST_ARG@27..28
// LITERAL@27..28 // LITERAL@27..28

View File

@ -6,7 +6,7 @@ use std::{
use either::Either; use either::Either;
use hir::{ use hir::{
ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError, ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError,
HirWrite, ModuleDef, ModuleDefId, Semantics, sym, HirWrite, InRealFile, ModuleDef, ModuleDefId, Semantics, sym,
}; };
use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder}; use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder};
use ide_db::{FxHashSet, text_edit::TextEdit}; use ide_db::{FxHashSet, text_edit::TextEdit};
@ -34,6 +34,7 @@ mod extern_block;
mod generic_param; mod generic_param;
mod implicit_drop; mod implicit_drop;
mod implicit_static; mod implicit_static;
mod implied_dyn_trait;
mod lifetime; mod lifetime;
mod param_name; mod param_name;
mod range_exclusive; mod range_exclusive;
@ -95,16 +96,16 @@ pub(crate) fn inlay_hints(
return acc; return acc;
}; };
let famous_defs = FamousDefs(&sema, scope.krate()); let famous_defs = FamousDefs(&sema, scope.krate());
let display_target = famous_defs.1.to_display_target(sema.db);
let ctx = &mut InlayHintCtx::default(); let ctx = &mut InlayHintCtx::default();
let mut hints = |event| { let mut hints = |event| {
if let Some(node) = handle_event(ctx, event) { if let Some(node) = handle_event(ctx, event) {
hints(&mut acc, ctx, &famous_defs, config, file_id, node); hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
} }
}; };
let mut preorder = file.preorder(); let mut preorder = file.preorder();
while let Some(event) = preorder.next() { while let Some(event) = preorder.next() {
// FIXME: This can miss some hints that require the parent of the range to calculate
if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none()) if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
{ {
preorder.skip_subtree(); preorder.skip_subtree();
@ -144,10 +145,12 @@ pub(crate) fn inlay_hints_resolve(
let famous_defs = FamousDefs(&sema, scope.krate()); let famous_defs = FamousDefs(&sema, scope.krate());
let mut acc = Vec::new(); let mut acc = Vec::new();
let display_target = famous_defs.1.to_display_target(sema.db);
let ctx = &mut InlayHintCtx::default(); let ctx = &mut InlayHintCtx::default();
let mut hints = |event| { let mut hints = |event| {
if let Some(node) = handle_event(ctx, event) { if let Some(node) = handle_event(ctx, event) {
hints(&mut acc, ctx, &famous_defs, config, file_id, node); hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
} }
}; };
@ -202,17 +205,19 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<S
fn hints( fn hints(
hints: &mut Vec<InlayHint>, hints: &mut Vec<InlayHint>,
ctx: &mut InlayHintCtx, ctx: &mut InlayHintCtx,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId, file_id: EditionedFileId,
display_target: DisplayTarget,
node: SyntaxNode, node: SyntaxNode,
) { ) {
let file_id = file_id.editioned_file_id(sema.db); closing_brace::hints(
let Some(krate) = sema.first_crate(file_id.file_id()) else { hints,
return; sema,
}; config,
let display_target = krate.to_display_target(sema.db); display_target,
closing_brace::hints(hints, sema, config, file_id, display_target, node.clone()); InRealFile { file_id, value: node.clone() },
);
if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) { if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
generic_param::hints(hints, famous_defs, config, any_has_generic_args); generic_param::hints(hints, famous_defs, config, any_has_generic_args);
} }
@ -231,18 +236,18 @@ fn hints(
closure_captures::hints(hints, famous_defs, config, it.clone()); closure_captures::hints(hints, famous_defs, config, it.clone());
closure_ret::hints(hints, famous_defs, config, display_target, it) closure_ret::hints(hints, famous_defs, config, display_target, it)
}, },
ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, file_id, it), ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
_ => Some(()), _ => Some(()),
} }
}, },
ast::Pat(it) => { ast::Pat(it) => {
binding_mode::hints(hints, famous_defs, config, file_id, &it); binding_mode::hints(hints, famous_defs, config, &it);
match it { match it {
ast::Pat::IdentPat(it) => { ast::Pat::IdentPat(it) => {
bind_pat::hints(hints, famous_defs, config, display_target, &it); bind_pat::hints(hints, famous_defs, config, display_target, &it);
} }
ast::Pat::RangePat(it) => { ast::Pat::RangePat(it) => {
range_exclusive::hints(hints, famous_defs, config, file_id, it); range_exclusive::hints(hints, famous_defs, config, it);
} }
_ => {} _ => {}
} }
@ -250,30 +255,38 @@ fn hints(
}, },
ast::Item(it) => match it { ast::Item(it) => match it {
ast::Item::Fn(it) => { ast::Item::Fn(it) => {
implicit_drop::hints(hints, famous_defs, config, file_id, &it); implicit_drop::hints(hints, famous_defs, config, display_target, &it);
if let Some(extern_block) = &ctx.extern_block_parent { if let Some(extern_block) = &ctx.extern_block_parent {
extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block); extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
} }
lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it) lifetime::fn_hints(hints, ctx, famous_defs, config, it)
}, },
ast::Item::Static(it) => { ast::Item::Static(it) => {
if let Some(extern_block) = &ctx.extern_block_parent { if let Some(extern_block) = &ctx.extern_block_parent {
extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block); extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
} }
implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)) implicit_static::hints(hints, famous_defs, config, Either::Left(it))
}, },
ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)), ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it), ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
_ => None, _ => None,
}, },
// FIXME: trait object type elisions // FIXME: trait object type elisions
ast::Type(ty) => match ty { ast::Type(ty) => match ty {
ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, file_id, ptr), ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, ptr),
ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path), ast::Type::PathType(path) => {
lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
Some(())
},
ast::Type::DynTraitType(dyn_) => {
implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
Some(())
},
_ => Some(()), _ => Some(()),
}, },
ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, file_id, it), ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it),
_ => Some(()), _ => Some(()),
} }
}; };
@ -438,6 +451,7 @@ pub enum InlayKind {
Parameter, Parameter,
GenericParameter, GenericParameter,
Type, Type,
Dyn,
Drop, Drop,
RangeExclusive, RangeExclusive,
ExternUnsafety, ExternUnsafety,

View File

@ -8,7 +8,6 @@ use hir::Mutability;
use ide_db::famous_defs::FamousDefs; use ide_db::famous_defs::FamousDefs;
use ide_db::text_edit::TextEditBuilder; use ide_db::text_edit::TextEditBuilder;
use span::EditionedFileId;
use syntax::ast::{self, AstNode}; use syntax::ast::{self, AstNode};
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
@ -17,7 +16,6 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
pat: &ast::Pat, pat: &ast::Pat,
) -> Option<()> { ) -> Option<()> {
if !config.binding_mode_hints { if !config.binding_mode_hints {

View File

@ -3,7 +3,6 @@
//! Currently this renders the implied `Sized` bound. //! Currently this renders the implied `Sized` bound.
use ide_db::{FileRange, famous_defs::FamousDefs}; use ide_db::{FileRange, famous_defs::FamousDefs};
use span::EditionedFileId;
use syntax::ast::{self, AstNode, HasTypeBounds}; use syntax::ast::{self, AstNode, HasTypeBounds};
use crate::{ use crate::{
@ -15,7 +14,6 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
params: ast::GenericParamList, params: ast::GenericParamList,
) -> Option<()> { ) -> Option<()> {
if !config.sized_bound { if !config.sized_bound {

View File

@ -3,9 +3,8 @@
//! fn g() { //! fn g() {
//! } /* fn g */ //! } /* fn g */
//! ``` //! ```
use hir::{DisplayTarget, HirDisplay, Semantics}; use hir::{DisplayTarget, HirDisplay, InRealFile, Semantics};
use ide_db::{FileRange, RootDatabase}; use ide_db::{FileRange, RootDatabase};
use span::EditionedFileId;
use syntax::{ use syntax::{
SyntaxKind, SyntaxNode, T, SyntaxKind, SyntaxNode, T,
ast::{self, AstNode, HasLoopBody, HasName}, ast::{self, AstNode, HasLoopBody, HasName},
@ -21,15 +20,14 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId,
display_target: DisplayTarget, display_target: DisplayTarget,
original_node: SyntaxNode, InRealFile { file_id, value: node }: InRealFile<SyntaxNode>,
) -> Option<()> { ) -> Option<()> {
let min_lines = config.closing_brace_hints_min_lines?; let min_lines = config.closing_brace_hints_min_lines?;
let name = |it: ast::Name| it.syntax().text_range(); let name = |it: ast::Name| it.syntax().text_range();
let mut node = original_node.clone(); let mut node = node.clone();
let mut closing_token; let mut closing_token;
let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
closing_token = item_list.r_curly_token()?; closing_token = item_list.r_curly_token()?;
@ -44,7 +42,7 @@ pub(super) fn hints(
let hint_text = match trait_ { let hint_text = match trait_ {
Some(tr) => format!( Some(tr) => format!(
"impl {} for {}", "impl {} for {}",
tr.name(sema.db).display(sema.db, file_id.edition()), tr.name(sema.db).display(sema.db, display_target.edition),
ty.display_truncated(sema.db, config.max_length, display_target, ty.display_truncated(sema.db, config.max_length, display_target,
)), )),
None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, display_target)), None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, display_target)),
@ -142,7 +140,8 @@ pub(super) fn hints(
return None; return None;
} }
let linked_location = name_range.map(|range| FileRange { file_id: file_id.into(), range }); let linked_location =
name_range.map(|range| FileRange { file_id: file_id.file_id(sema.db), range });
acc.push(InlayHint { acc.push(InlayHint {
range: closing_token.text_range(), range: closing_token.text_range(),
kind: InlayKind::ClosingBrace, kind: InlayKind::ClosingBrace,
@ -151,7 +150,7 @@ pub(super) fn hints(
position: InlayHintPosition::After, position: InlayHintPosition::After,
pad_left: true, pad_left: true,
pad_right: false, pad_right: false,
resolve_parent: Some(original_node.text_range()), resolve_parent: Some(node.text_range()),
}); });
None None

View File

@ -7,7 +7,6 @@
use hir::Semantics; use hir::Semantics;
use ide_db::text_edit::TextEdit; use ide_db::text_edit::TextEdit;
use ide_db::{RootDatabase, famous_defs::FamousDefs}; use ide_db::{RootDatabase, famous_defs::FamousDefs};
use span::EditionedFileId;
use syntax::ast::{self, AstNode, HasName}; use syntax::ast::{self, AstNode, HasName};
use crate::{ use crate::{
@ -19,7 +18,6 @@ pub(super) fn enum_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_: EditionedFileId,
enum_: ast::Enum, enum_: ast::Enum,
) -> Option<()> { ) -> Option<()> {
if let DiscriminantHints::Never = config.discriminant_hints { if let DiscriminantHints::Never = config.discriminant_hints {

View File

@ -1,6 +1,5 @@
//! Extern block hints //! Extern block hints
use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit};
use span::EditionedFileId;
use syntax::{AstNode, SyntaxToken, ast}; use syntax::{AstNode, SyntaxToken, ast};
use crate::{InlayHint, InlayHintsConfig}; use crate::{InlayHint, InlayHintsConfig};
@ -9,7 +8,6 @@ pub(super) fn extern_block_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
extern_block: ast::ExternBlock, extern_block: ast::ExternBlock,
) -> Option<()> { ) -> Option<()> {
if extern_block.unsafe_token().is_some() { if extern_block.unsafe_token().is_some() {
@ -36,7 +34,6 @@ pub(super) fn fn_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
fn_: &ast::Fn, fn_: &ast::Fn,
extern_block: &ast::ExternBlock, extern_block: &ast::ExternBlock,
) -> Option<()> { ) -> Option<()> {
@ -55,7 +52,6 @@ pub(super) fn static_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
static_: &ast::Static, static_: &ast::Static,
extern_block: &ast::ExternBlock, extern_block: &ast::ExternBlock,
) -> Option<()> { ) -> Option<()> {

View File

@ -12,7 +12,6 @@ use hir::{
}; };
use ide_db::{FileRange, famous_defs::FamousDefs}; use ide_db::{FileRange, famous_defs::FamousDefs};
use span::EditionedFileId;
use syntax::{ use syntax::{
ToSmolStr, ToSmolStr,
ast::{self, AstNode}, ast::{self, AstNode},
@ -25,7 +24,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>, FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId, display_target: hir::DisplayTarget,
node: &ast::Fn, node: &ast::Fn,
) -> Option<()> { ) -> Option<()> {
if !config.implicit_drop_hints { if !config.implicit_drop_hints {
@ -94,7 +93,7 @@ pub(super) fn hints(
MirSpan::Unknown => continue, MirSpan::Unknown => continue,
}; };
let binding = &hir.bindings[binding_idx]; let binding = &hir.bindings[binding_idx];
let name = binding.name.display_no_db(file_id.edition()).to_smolstr(); let name = binding.name.display_no_db(display_target.edition).to_smolstr();
if name.starts_with("<ra@") { if name.starts_with("<ra@") {
continue; // Ignore desugared variables continue; // Ignore desugared variables
} }

View File

@ -5,7 +5,6 @@
use either::Either; use either::Either;
use ide_db::famous_defs::FamousDefs; use ide_db::famous_defs::FamousDefs;
use ide_db::text_edit::TextEdit; use ide_db::text_edit::TextEdit;
use span::EditionedFileId;
use syntax::{ use syntax::{
SyntaxKind, SyntaxKind,
ast::{self, AstNode}, ast::{self, AstNode},
@ -17,7 +16,6 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>, FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
statik_or_const: Either<ast::Static, ast::Const>, statik_or_const: Either<ast::Static, ast::Const>,
) -> Option<()> { ) -> Option<()> {
if config.lifetime_elision_hints != LifetimeElisionHints::Always { if config.lifetime_elision_hints != LifetimeElisionHints::Always {

View File

@ -0,0 +1,133 @@
//! Implementation of trait bound hints.
//!
//! Currently this renders the implied `Sized` bound.
use either::Either;
use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit};
use syntax::ast::{self, AstNode};
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
path: Either<ast::PathType, ast::DynTraitType>,
) -> Option<()> {
let parent = path.syntax().parent()?;
let range = match path {
Either::Left(path) => {
let paren =
parent.ancestors().take_while(|it| ast::ParenType::can_cast(it.kind())).last();
let parent = paren.as_ref().and_then(|it| it.parent()).unwrap_or(parent);
if ast::TypeBound::can_cast(parent.kind())
|| ast::TypeAnchor::can_cast(parent.kind())
|| ast::Impl::cast(parent)
.and_then(|it| it.trait_())
.is_some_and(|it| it.syntax() == path.syntax())
{
return None;
}
sema.resolve_trait(&path.path()?)?;
paren.map_or_else(|| path.syntax().text_range(), |it| it.text_range())
}
Either::Right(dyn_) => {
if dyn_.dyn_token().is_some() {
return None;
}
dyn_.syntax().text_range()
}
};
acc.push(InlayHint {
range,
kind: InlayKind::Dyn,
label: InlayHintLabel::simple("dyn", None, None),
text_edit: Some(
config.lazy_text_edit(|| TextEdit::insert(range.start(), "dyn ".to_owned())),
),
position: InlayHintPosition::Before,
pad_left: false,
pad_right: true,
resolve_parent: Some(range),
});
Some(())
}
#[cfg(test)]
mod tests {
use expect_test::expect;
use crate::inlay_hints::InlayHintsConfig;
use crate::inlay_hints::tests::{DISABLED_CONFIG, check_edit, check_with_config};
#[track_caller]
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
check_with_config(InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, ra_fixture);
}
#[test]
fn path_works() {
check(
r#"
struct S {}
trait T {}
fn foo(_: T, _: dyn T, _: S) {}
// ^ dyn
fn foo(_: &T, _: for<'a> T) {}
// ^ dyn
// ^ dyn
impl T {}
// ^ dyn
impl T for (T) {}
// ^^^ dyn
"#,
);
}
#[test]
fn missing_dyn_bounds() {
check(
r#"
trait T {}
fn foo(
_: T + T,
// ^^^^^ dyn
_: T + 'a,
// ^^^^^^ dyn
_: 'a + T,
// ^^^^^^ dyn
_: &(T + T)
// ^^^^^ dyn
_: &mut (T + T)
// ^^^^^ dyn
_: *mut (T),
// ^^^ dyn
) {}
"#,
);
}
#[test]
fn edit() {
check_edit(
DISABLED_CONFIG,
r#"
trait T {}
fn foo(
_: &mut T
) {}
"#,
expect![[r#"
trait T {}
fn foo(
_: &mut dyn T
) {}
"#]],
);
}
}

View File

@ -6,7 +6,6 @@ use std::iter;
use ide_db::{FxHashMap, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty}; use ide_db::{FxHashMap, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty};
use itertools::Itertools; use itertools::Itertools;
use span::EditionedFileId;
use syntax::{SmolStr, format_smolstr}; use syntax::{SmolStr, format_smolstr};
use syntax::{ use syntax::{
SyntaxKind, SyntaxToken, SyntaxKind, SyntaxToken,
@ -23,7 +22,6 @@ pub(super) fn fn_hints(
ctx: &mut InlayHintCtx, ctx: &mut InlayHintCtx,
fd: &FamousDefs<'_, '_>, fd: &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId,
func: ast::Fn, func: ast::Fn,
) -> Option<()> { ) -> Option<()> {
if config.lifetime_elision_hints == LifetimeElisionHints::Never { if config.lifetime_elision_hints == LifetimeElisionHints::Never {
@ -40,7 +38,6 @@ pub(super) fn fn_hints(
ctx, ctx,
fd, fd,
config, config,
file_id,
param_list.params().filter_map(|it| { param_list.params().filter_map(|it| {
Some(( Some((
it.pat().and_then(|it| match it { it.pat().and_then(|it| match it {
@ -74,7 +71,6 @@ pub(super) fn fn_ptr_hints(
ctx: &mut InlayHintCtx, ctx: &mut InlayHintCtx,
fd: &FamousDefs<'_, '_>, fd: &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId,
func: ast::FnPtrType, func: ast::FnPtrType,
) -> Option<()> { ) -> Option<()> {
if config.lifetime_elision_hints == LifetimeElisionHints::Never { if config.lifetime_elision_hints == LifetimeElisionHints::Never {
@ -97,7 +93,6 @@ pub(super) fn fn_ptr_hints(
ctx, ctx,
fd, fd,
config, config,
file_id,
param_list.params().filter_map(|it| { param_list.params().filter_map(|it| {
Some(( Some((
it.pat().and_then(|it| match it { it.pat().and_then(|it| match it {
@ -140,8 +135,7 @@ pub(super) fn fn_path_hints(
ctx: &mut InlayHintCtx, ctx: &mut InlayHintCtx,
fd: &FamousDefs<'_, '_>, fd: &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: EditionedFileId, func: &ast::PathType,
func: ast::PathType,
) -> Option<()> { ) -> Option<()> {
if config.lifetime_elision_hints == LifetimeElisionHints::Never { if config.lifetime_elision_hints == LifetimeElisionHints::Never {
return None; return None;
@ -163,7 +157,6 @@ pub(super) fn fn_path_hints(
ctx, ctx,
fd, fd,
config, config,
file_id,
param_list.type_args().filter_map(|it| Some((None, it.ty()?))), param_list.type_args().filter_map(|it| Some((None, it.ty()?))),
generic_param_list, generic_param_list,
ret_type, ret_type,
@ -202,7 +195,6 @@ fn hints_(
ctx: &mut InlayHintCtx, ctx: &mut InlayHintCtx,
FamousDefs(_, _): &FamousDefs<'_, '_>, FamousDefs(_, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>, params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>,
generic_param_list: Option<ast::GenericParamList>, generic_param_list: Option<ast::GenericParamList>,
ret_type: Option<ast::RetType>, ret_type: Option<ast::RetType>,

View File

@ -4,7 +4,6 @@
//! if let ../* < */100 = 50 {} //! if let ../* < */100 = 50 {}
//! ``` //! ```
use ide_db::famous_defs::FamousDefs; use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
use syntax::{SyntaxToken, T, ast}; use syntax::{SyntaxToken, T, ast};
use crate::{InlayHint, InlayHintsConfig}; use crate::{InlayHint, InlayHintsConfig};
@ -13,7 +12,6 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>, FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
_file_id: EditionedFileId,
range: impl ast::RangeItem, range: impl ast::RangeItem,
) -> Option<()> { ) -> Option<()> {
(config.range_exclusive_hints && range.end().is_some()) (config.range_exclusive_hints && range.end().is_some())

View File

@ -89,7 +89,9 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<Completed
// test qual_paths // test qual_paths
// type X = <A as B>::Output; // type X = <A as B>::Output;
// fn foo() { <usize as Default>::default(); } // fn foo() { <usize as Default>::default(); }
if first && p.eat(T![<]) { if first && p.at(T![<]) {
let m = p.start();
p.bump(T![<]);
// test_err angled_path_without_qual // test_err angled_path_without_qual
// type X = <()>; // type X = <()>;
// type Y = <A as B>; // type Y = <A as B>;
@ -102,6 +104,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<Completed
} }
} }
p.expect(T![>]); p.expect(T![>]);
m.complete(p, TYPE_ANCHOR);
if !p.at(T![::]) { if !p.at(T![::]) {
p.error("expected `::`"); p.error("expected `::`");
} }

View File

@ -291,6 +291,7 @@ pub enum SyntaxKind {
TUPLE_STRUCT_PAT, TUPLE_STRUCT_PAT,
TUPLE_TYPE, TUPLE_TYPE,
TYPE_ALIAS, TYPE_ALIAS,
TYPE_ANCHOR,
TYPE_ARG, TYPE_ARG,
TYPE_BOUND, TYPE_BOUND,
TYPE_BOUND_LIST, TYPE_BOUND_LIST,
@ -463,6 +464,7 @@ impl SyntaxKind {
| TUPLE_STRUCT_PAT | TUPLE_STRUCT_PAT
| TUPLE_TYPE | TUPLE_TYPE
| TYPE_ALIAS | TYPE_ALIAS
| TYPE_ANCHOR
| TYPE_ARG | TYPE_ARG
| TYPE_BOUND | TYPE_BOUND
| TYPE_BOUND_LIST | TYPE_BOUND_LIST

View File

@ -10,11 +10,12 @@ SOURCE_FILE
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
TUPLE_TYPE L_ANGLE "<"
L_PAREN "(" TUPLE_TYPE
R_PAREN ")" L_PAREN "("
R_ANGLE ">" R_PAREN ")"
R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
TYPE_ALIAS TYPE_ALIAS
@ -28,21 +29,22 @@ SOURCE_FILE
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "A" NAME_REF
WHITESPACE " " IDENT "A"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "B" NAME_REF
R_ANGLE ">" IDENT "B"
R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
error 13: expected `::` error 13: expected `::`

View File

@ -88,13 +88,14 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Foo" NAME_REF
R_ANGLE ">" IDENT "Foo"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
@ -119,21 +120,22 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Foo" NAME_REF
WHITESPACE " " IDENT "Foo"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Trait" NAME_REF
R_ANGLE ">" IDENT "Trait"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -11,21 +11,22 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "A" NAME_REF
WHITESPACE " " IDENT "A"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "B" NAME_REF
R_ANGLE ">" IDENT "B"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
@ -51,21 +52,22 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "usize" NAME_REF
WHITESPACE " " IDENT "usize"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Default" NAME_REF
R_ANGLE ">" IDENT "Default"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -19,10 +19,11 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
INFER_TYPE L_ANGLE "<"
UNDERSCORE "_" INFER_TYPE
R_ANGLE ">" UNDERSCORE "_"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -84,21 +84,22 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "T" NAME_REF
WHITESPACE " " IDENT "T"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Iterator" NAME_REF
R_ANGLE ">" IDENT "Iterator"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -45,21 +45,22 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "S" NAME_REF
WHITESPACE " " IDENT "S"
AS_KW "as" WHITESPACE " "
WHITESPACE " " AS_KW "as"
PATH_TYPE WHITESPACE " "
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Iterator" NAME_REF
R_ANGLE ">" IDENT "Iterator"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -107,13 +107,14 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
PATH_TYPE L_ANGLE "<"
PATH PATH_TYPE
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Foo" NAME_REF
R_ANGLE ">" IDENT "Foo"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -288,26 +288,27 @@ SOURCE_FILE
PATH PATH
PATH PATH
PATH_SEGMENT PATH_SEGMENT
L_ANGLE "<" TYPE_ANCHOR
REF_TYPE L_ANGLE "<"
AMP "&" REF_TYPE
LIFETIME AMP "&"
LIFETIME_IDENT "'a" LIFETIME
LIFETIME_IDENT "'a"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "T"
WHITESPACE " "
AS_KW "as"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "T" IDENT "Baz"
WHITESPACE " " R_ANGLE ">"
AS_KW "as"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Baz"
R_ANGLE ">"
COLON2 "::" COLON2 "::"
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF

View File

@ -39,7 +39,10 @@ PathSegment =
| NameRef GenericArgList? | NameRef GenericArgList?
| NameRef ParenthesizedArgList RetType? | NameRef ParenthesizedArgList RetType?
| NameRef ReturnTypeSyntax | NameRef ReturnTypeSyntax
| '<' Type ('as' PathType)? '>' | TypeAnchor
TypeAnchor =
'<' Type ('as' PathType)? '>'
ReturnTypeSyntax = ReturnTypeSyntax =
'(' '..' ')' '(' '..' ')'
@ -98,7 +101,7 @@ WhereClause =
'where' predicates:(WherePred (',' WherePred)* ','?) 'where' predicates:(WherePred (',' WherePred)* ','?)
WherePred = WherePred =
('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList?
//*************************// //*************************//

View File

@ -1232,21 +1232,13 @@ impl PathSegment {
support::child(&self.syntax) support::child(&self.syntax)
} }
#[inline] #[inline]
pub fn path_type(&self) -> Option<PathType> { support::child(&self.syntax) }
#[inline]
pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) } pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn return_type_syntax(&self) -> Option<ReturnTypeSyntax> { support::child(&self.syntax) } pub fn return_type_syntax(&self) -> Option<ReturnTypeSyntax> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } pub fn type_anchor(&self) -> Option<TypeAnchor> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) } pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
#[inline]
pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
#[inline]
pub fn r_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>]) }
#[inline]
pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) }
} }
pub struct PathType { pub struct PathType {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
@ -1739,6 +1731,21 @@ impl TypeAlias {
#[inline] #[inline]
pub fn type_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![type]) } pub fn type_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![type]) }
} }
pub struct TypeAnchor {
pub(crate) syntax: SyntaxNode,
}
impl TypeAnchor {
#[inline]
pub fn path_type(&self) -> Option<PathType> { support::child(&self.syntax) }
#[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
#[inline]
pub fn r_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>]) }
#[inline]
pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) }
}
pub struct TypeArg { pub struct TypeArg {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
} }
@ -7108,6 +7115,42 @@ impl fmt::Debug for TypeAlias {
f.debug_struct("TypeAlias").field("syntax", &self.syntax).finish() f.debug_struct("TypeAlias").field("syntax", &self.syntax).finish()
} }
} }
impl AstNode for TypeAnchor {
#[inline]
fn kind() -> SyntaxKind
where
Self: Sized,
{
TYPE_ANCHOR
}
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == TYPE_ANCHOR }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl hash::Hash for TypeAnchor {
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
}
impl Eq for TypeAnchor {}
impl PartialEq for TypeAnchor {
fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
}
impl Clone for TypeAnchor {
fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
}
impl fmt::Debug for TypeAnchor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeAnchor").field("syntax", &self.syntax).finish()
}
}
impl AstNode for TypeArg { impl AstNode for TypeArg {
#[inline] #[inline]
fn kind() -> SyntaxKind fn kind() -> SyntaxKind
@ -10624,6 +10667,11 @@ impl std::fmt::Display for TypeAlias {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)
} }
} }
impl std::fmt::Display for TypeAnchor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for TypeArg { impl std::fmt::Display for TypeArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)

View File

@ -276,18 +276,15 @@ impl ast::PathSegment {
_ => PathSegmentKind::Name(name_ref), _ => PathSegmentKind::Name(name_ref),
} }
} else { } else {
match self.syntax().first_child_or_token()?.kind() { let anchor = self.type_anchor()?;
T![<] => { // FIXME: Move this over to `ast::TypeAnchor`
// <T> or <T as Trait> // <T> or <T as Trait>
// T is any TypeRef, Trait has to be a PathType // T is any TypeRef, Trait has to be a PathType
let mut type_refs = let mut type_refs =
self.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); anchor.syntax().children().filter(|node| ast::Type::can_cast(node.kind()));
let type_ref = type_refs.next().and_then(ast::Type::cast); let type_ref = type_refs.next().and_then(ast::Type::cast);
let trait_ref = type_refs.next().and_then(ast::PathType::cast); let trait_ref = type_refs.next().and_then(ast::PathType::cast);
PathSegmentKind::Type { type_ref, trait_ref } PathSegmentKind::Type { type_ref, trait_ref }
}
_ => return None,
}
}; };
Some(res) Some(res)
} }
@ -473,7 +470,7 @@ impl ast::Impl {
// [#15778](https://github.com/rust-lang/rust-analyzer/issues/15778) // [#15778](https://github.com/rust-lang/rust-analyzer/issues/15778)
impl ast::PathSegment { impl ast::PathSegment {
pub fn qualifying_trait(&self) -> Option<ast::PathType> { pub fn qualifying_trait(&self) -> Option<ast::PathType> {
let mut path_types = support::children(self.syntax()); let mut path_types = support::children(self.type_anchor()?.syntax());
let first = path_types.next()?; let first = path_types.next()?;
path_types.next().or(Some(first)) path_types.next().or(Some(first))
} }