From d2164fe08bf8bb4fb69cc694391f43404c3b4973 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 3 Jun 2025 12:03:01 +0200 Subject: [PATCH 1/3] Remove unnecessary parameters in inlay-hint computation --- crates/ide/src/inlay_hints.rs | 57 +++++++++++-------- crates/ide/src/inlay_hints/binding_mode.rs | 2 - crates/ide/src/inlay_hints/bounds.rs | 2 - crates/ide/src/inlay_hints/closing_brace.rs | 15 +++-- crates/ide/src/inlay_hints/discriminant.rs | 2 - crates/ide/src/inlay_hints/extern_block.rs | 4 -- crates/ide/src/inlay_hints/implicit_drop.rs | 5 +- crates/ide/src/inlay_hints/implicit_static.rs | 2 - crates/ide/src/inlay_hints/lifetime.rs | 8 --- crates/ide/src/inlay_hints/range_exclusive.rs | 2 - 10 files changed, 41 insertions(+), 58 deletions(-) diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 82704af647..d05a36c5f4 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -6,7 +6,7 @@ use std::{ use either::Either; use hir::{ 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::{FxHashSet, text_edit::TextEdit}; @@ -95,16 +95,16 @@ pub(crate) fn inlay_hints( return acc; }; let famous_defs = FamousDefs(&sema, scope.krate()); + let display_target = famous_defs.1.to_display_target(sema.db); let ctx = &mut InlayHintCtx::default(); let mut hints = |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(); 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()) { preorder.skip_subtree(); @@ -144,10 +144,12 @@ pub(crate) fn inlay_hints_resolve( let famous_defs = FamousDefs(&sema, scope.krate()); let mut acc = Vec::new(); + let display_target = famous_defs.1.to_display_target(sema.db); + let ctx = &mut InlayHintCtx::default(); let mut hints = |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 +204,19 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent) -> Option, ctx: &mut InlayHintCtx, - famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>, config: &InlayHintsConfig, file_id: EditionedFileId, + display_target: DisplayTarget, node: SyntaxNode, ) { - let file_id = file_id.editioned_file_id(sema.db); - let Some(krate) = sema.first_crate(file_id.file_id()) else { - return; - }; - let display_target = krate.to_display_target(sema.db); - closing_brace::hints(hints, sema, config, file_id, display_target, node.clone()); + closing_brace::hints( + hints, + sema, + config, + display_target, + InRealFile { file_id, value: 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); } @@ -231,18 +235,18 @@ fn hints( closure_captures::hints(hints, famous_defs, config, it.clone()); 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(()), } }, ast::Pat(it) => { - binding_mode::hints(hints, famous_defs, config, file_id, &it); + binding_mode::hints(hints, famous_defs, config, &it); match it { ast::Pat::IdentPat(it) => { bind_pat::hints(hints, famous_defs, config, display_target, &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 +254,33 @@ fn hints( }, ast::Item(it) => match 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 { - 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) => { 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::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), - ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, 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, it), + ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it), _ => None, }, // FIXME: trait object type elisions ast::Type(ty) => match ty { - ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, file_id, ptr), - ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path), + 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, path); + 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(()), } }; diff --git a/crates/ide/src/inlay_hints/binding_mode.rs b/crates/ide/src/inlay_hints/binding_mode.rs index d291732068..169ab92342 100644 --- a/crates/ide/src/inlay_hints/binding_mode.rs +++ b/crates/ide/src/inlay_hints/binding_mode.rs @@ -8,7 +8,6 @@ use hir::Mutability; use ide_db::famous_defs::FamousDefs; use ide_db::text_edit::TextEditBuilder; -use span::EditionedFileId; use syntax::ast::{self, AstNode}; use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; @@ -17,7 +16,6 @@ pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, pat: &ast::Pat, ) -> Option<()> { if !config.binding_mode_hints { diff --git a/crates/ide/src/inlay_hints/bounds.rs b/crates/ide/src/inlay_hints/bounds.rs index 8ddbfaeffe..b9a98f88be 100644 --- a/crates/ide/src/inlay_hints/bounds.rs +++ b/crates/ide/src/inlay_hints/bounds.rs @@ -3,7 +3,6 @@ //! Currently this renders the implied `Sized` bound. use ide_db::{FileRange, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::ast::{self, AstNode, HasTypeBounds}; use crate::{ @@ -15,7 +14,6 @@ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, params: ast::GenericParamList, ) -> Option<()> { if !config.sized_bound { diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs index 2ec85da4a4..ca3a982760 100644 --- a/crates/ide/src/inlay_hints/closing_brace.rs +++ b/crates/ide/src/inlay_hints/closing_brace.rs @@ -3,9 +3,8 @@ //! fn g() { //! } /* fn g */ //! ``` -use hir::{DisplayTarget, HirDisplay, Semantics}; +use hir::{DisplayTarget, HirDisplay, InRealFile, Semantics}; use ide_db::{FileRange, RootDatabase}; -use span::EditionedFileId; use syntax::{ SyntaxKind, SyntaxNode, T, ast::{self, AstNode, HasLoopBody, HasName}, @@ -21,15 +20,14 @@ pub(super) fn hints( acc: &mut Vec, sema: &Semantics<'_, RootDatabase>, config: &InlayHintsConfig, - file_id: EditionedFileId, display_target: DisplayTarget, - original_node: SyntaxNode, + InRealFile { file_id, value: node }: InRealFile, ) -> Option<()> { let min_lines = config.closing_brace_hints_min_lines?; 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 (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { closing_token = item_list.r_curly_token()?; @@ -44,7 +42,7 @@ pub(super) fn hints( let hint_text = match trait_ { Some(tr) => format!( "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, )), None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, display_target)), @@ -142,7 +140,8 @@ pub(super) fn hints( 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 { range: closing_token.text_range(), kind: InlayKind::ClosingBrace, @@ -151,7 +150,7 @@ pub(super) fn hints( position: InlayHintPosition::After, pad_left: true, pad_right: false, - resolve_parent: Some(original_node.text_range()), + resolve_parent: Some(node.text_range()), }); None diff --git a/crates/ide/src/inlay_hints/discriminant.rs b/crates/ide/src/inlay_hints/discriminant.rs index 827a0438dd..a2a702835a 100644 --- a/crates/ide/src/inlay_hints/discriminant.rs +++ b/crates/ide/src/inlay_hints/discriminant.rs @@ -7,7 +7,6 @@ use hir::Semantics; use ide_db::text_edit::TextEdit; use ide_db::{RootDatabase, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::ast::{self, AstNode, HasName}; use crate::{ @@ -19,7 +18,6 @@ pub(super) fn enum_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _: EditionedFileId, enum_: ast::Enum, ) -> Option<()> { if let DiscriminantHints::Never = config.discriminant_hints { diff --git a/crates/ide/src/inlay_hints/extern_block.rs b/crates/ide/src/inlay_hints/extern_block.rs index 20f54b2cd1..88152bf3e3 100644 --- a/crates/ide/src/inlay_hints/extern_block.rs +++ b/crates/ide/src/inlay_hints/extern_block.rs @@ -1,6 +1,5 @@ //! Extern block hints use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; -use span::EditionedFileId; use syntax::{AstNode, SyntaxToken, ast}; use crate::{InlayHint, InlayHintsConfig}; @@ -9,7 +8,6 @@ pub(super) fn extern_block_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, extern_block: ast::ExternBlock, ) -> Option<()> { if extern_block.unsafe_token().is_some() { @@ -36,7 +34,6 @@ pub(super) fn fn_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, fn_: &ast::Fn, extern_block: &ast::ExternBlock, ) -> Option<()> { @@ -55,7 +52,6 @@ pub(super) fn static_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, static_: &ast::Static, extern_block: &ast::ExternBlock, ) -> Option<()> { diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs index f52e27946f..bf4688e9d8 100644 --- a/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/crates/ide/src/inlay_hints/implicit_drop.rs @@ -12,7 +12,6 @@ use hir::{ }; use ide_db::{FileRange, famous_defs::FamousDefs}; -use span::EditionedFileId; use syntax::{ ToSmolStr, ast::{self, AstNode}, @@ -25,7 +24,7 @@ pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, + display_target: hir::DisplayTarget, node: &ast::Fn, ) -> Option<()> { if !config.implicit_drop_hints { @@ -94,7 +93,7 @@ pub(super) fn hints( MirSpan::Unknown => continue, }; 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(", FamousDefs(_sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, statik_or_const: Either, ) -> Option<()> { if config.lifetime_elision_hints != LifetimeElisionHints::Always { diff --git a/crates/ide/src/inlay_hints/lifetime.rs b/crates/ide/src/inlay_hints/lifetime.rs index baba49a427..939fe02691 100644 --- a/crates/ide/src/inlay_hints/lifetime.rs +++ b/crates/ide/src/inlay_hints/lifetime.rs @@ -6,7 +6,6 @@ use std::iter; use ide_db::{FxHashMap, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty}; use itertools::Itertools; -use span::EditionedFileId; use syntax::{SmolStr, format_smolstr}; use syntax::{ SyntaxKind, SyntaxToken, @@ -23,7 +22,6 @@ pub(super) fn fn_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, func: ast::Fn, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -40,7 +38,6 @@ pub(super) fn fn_hints( ctx, fd, config, - file_id, param_list.params().filter_map(|it| { Some(( it.pat().and_then(|it| match it { @@ -74,7 +71,6 @@ pub(super) fn fn_ptr_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, func: ast::FnPtrType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -97,7 +93,6 @@ pub(super) fn fn_ptr_hints( ctx, fd, config, - file_id, param_list.params().filter_map(|it| { Some(( it.pat().and_then(|it| match it { @@ -140,7 +135,6 @@ pub(super) fn fn_path_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: EditionedFileId, func: ast::PathType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -163,7 +157,6 @@ pub(super) fn fn_path_hints( ctx, fd, config, - file_id, param_list.type_args().filter_map(|it| Some((None, it.ty()?))), generic_param_list, ret_type, @@ -202,7 +195,6 @@ fn hints_( ctx: &mut InlayHintCtx, FamousDefs(_, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, params: impl Iterator, ast::Type)>, generic_param_list: Option, ret_type: Option, diff --git a/crates/ide/src/inlay_hints/range_exclusive.rs b/crates/ide/src/inlay_hints/range_exclusive.rs index d67d845884..47bd6d737f 100644 --- a/crates/ide/src/inlay_hints/range_exclusive.rs +++ b/crates/ide/src/inlay_hints/range_exclusive.rs @@ -4,7 +4,6 @@ //! if let ../* < */100 = 50 {} //! ``` use ide_db::famous_defs::FamousDefs; -use span::EditionedFileId; use syntax::{SyntaxToken, T, ast}; use crate::{InlayHint, InlayHintsConfig}; @@ -13,7 +12,6 @@ pub(super) fn hints( acc: &mut Vec, FamousDefs(_sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - _file_id: EditionedFileId, range: impl ast::RangeItem, ) -> Option<()> { (config.range_exclusive_hints && range.end().is_some()) From 7c3de05e3ac33a1b965bbdd3703243780143b9d2 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Jun 2025 11:30:43 +0200 Subject: [PATCH 2/3] Give path segment type anchors their own grammar rule --- .../macro_expansion_tests/mbe/regression.rs | 11 ++-- crates/parser/src/grammar/paths.rs | 5 +- crates/parser/src/syntax_kind/generated.rs | 2 + .../inline/err/angled_path_without_qual.rast | 42 ++++++------ .../test_data/parser/inline/ok/call_expr.rast | 46 ++++++------- .../parser/inline/ok/qual_paths.rast | 62 ++++++++--------- .../inline/ok/type_path_in_pattern.rast | 9 +-- .../parser/inline/ok/where_clause.rast | 31 ++++----- .../parser/ok/0036_fully_qualified.rast | 31 ++++----- .../parser/ok/0042_ufcs_call_list.rast | 15 +++-- .../parser/ok/0067_where_for_pred.rast | 31 ++++----- crates/syntax/rust.ungram | 7 +- crates/syntax/src/ast/generated/nodes.rs | 66 ++++++++++++++++--- crates/syntax/src/ast/node_ext.rs | 23 +++---- 14 files changed, 223 insertions(+), 158 deletions(-) diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index cb4fcd887d..2cc3ca8c75 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -931,11 +931,12 @@ pub fn new() { // PATH_TYPE@23..26 // PATH@23..26 // PATH_SEGMENT@23..26 -// L_ANGLE@23..24 "<" -// PAREN_TYPE@24..26 -// L_PAREN@24..25 "(" -// ERROR@25..26 -// INT_NUMBER@25..26 "8" +// TYPE_ANCHOR@23..26 +// L_ANGLE@23..24 "<" +// PAREN_TYPE@24..26 +// L_PAREN@24..25 "(" +// ERROR@25..26 +// INT_NUMBER@25..26 "8" // PLUS@26..27 "+" // CONST_ARG@27..28 // LITERAL@27..28 diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 770827c6b0..e628bcc056 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -89,7 +89,9 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option::Output; // fn foo() { ::default(); } - if first && p.eat(T![<]) { + if first && p.at(T![<]) { + let m = p.start(); + p.bump(T![<]); // test_err angled_path_without_qual // type X = <()>; // type Y = ; @@ -102,6 +104,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option]); + m.complete(p, TYPE_ANCHOR); if !p.at(T![::]) { p.error("expected `::`"); } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index b1727509b1..f534546ea0 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -291,6 +291,7 @@ pub enum SyntaxKind { TUPLE_STRUCT_PAT, TUPLE_TYPE, TYPE_ALIAS, + TYPE_ANCHOR, TYPE_ARG, TYPE_BOUND, TYPE_BOUND_LIST, @@ -463,6 +464,7 @@ impl SyntaxKind { | TUPLE_STRUCT_PAT | TUPLE_TYPE | TYPE_ALIAS + | TYPE_ANCHOR | TYPE_ARG | TYPE_BOUND | TYPE_BOUND_LIST diff --git a/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast b/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast index 0529e9750e..53fbe0b615 100644 --- a/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast +++ b/crates/parser/test_data/parser/inline/err/angled_path_without_qual.rast @@ -10,11 +10,12 @@ SOURCE_FILE PATH_TYPE PATH PATH_SEGMENT - L_ANGLE "<" - TUPLE_TYPE - L_PAREN "(" - R_PAREN ")" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" TYPE_ALIAS @@ -28,21 +29,22 @@ SOURCE_FILE PATH_TYPE PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "A" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "B" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "B" + R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" error 13: expected `::` diff --git a/crates/parser/test_data/parser/inline/ok/call_expr.rast b/crates/parser/test_data/parser/inline/ok/call_expr.rast index 19cc8d5ac7..7c1d894f7e 100644 --- a/crates/parser/test_data/parser/inline/ok/call_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/call_expr.rast @@ -88,13 +88,14 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF @@ -119,21 +120,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/qual_paths.rast b/crates/parser/test_data/parser/inline/ok/qual_paths.rast index 8c66cfe599..10f8a6a751 100644 --- a/crates/parser/test_data/parser/inline/ok/qual_paths.rast +++ b/crates/parser/test_data/parser/inline/ok/qual_paths.rast @@ -11,21 +11,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "A" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "B" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "B" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF @@ -51,21 +52,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "usize" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Default" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "usize" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Default" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast b/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast index 297f7575ca..3d27afa5ec 100644 --- a/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast +++ b/crates/parser/test_data/parser/inline/ok/type_path_in_pattern.rast @@ -19,10 +19,11 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - INFER_TYPE - UNDERSCORE "_" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + INFER_TYPE + UNDERSCORE "_" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/where_clause.rast b/crates/parser/test_data/parser/inline/ok/where_clause.rast index a3cbe457e1..9adfe2caa7 100644 --- a/crates/parser/test_data/parser/inline/ok/where_clause.rast +++ b/crates/parser/test_data/parser/inline/ok/where_clause.rast @@ -84,21 +84,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "T" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Iterator" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Iterator" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0036_fully_qualified.rast b/crates/parser/test_data/parser/ok/0036_fully_qualified.rast index 9382020e2f..2fecb1cc47 100644 --- a/crates/parser/test_data/parser/ok/0036_fully_qualified.rast +++ b/crates/parser/test_data/parser/ok/0036_fully_qualified.rast @@ -45,21 +45,22 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Iterator" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Iterator" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast b/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast index a536b0e881..d1d1ffacf0 100644 --- a/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast +++ b/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast @@ -107,13 +107,14 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - R_ANGLE ">" + TYPE_ANCHOR + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast index cd3b21ae94..8bf1090f9c 100644 --- a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast +++ b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast @@ -288,26 +288,27 @@ SOURCE_FILE PATH PATH PATH_SEGMENT - L_ANGLE "<" - REF_TYPE - AMP "&" - LIFETIME - LIFETIME_IDENT "'a" + TYPE_ANCHOR + L_ANGLE "<" + REF_TYPE + AMP "&" + LIFETIME + LIFETIME_IDENT "'a" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + WHITESPACE " " + AS_KW "as" WHITESPACE " " PATH_TYPE PATH PATH_SEGMENT NAME_REF - IDENT "T" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Baz" - R_ANGLE ">" + IDENT "Baz" + R_ANGLE ">" COLON2 "::" PATH_SEGMENT NAME_REF diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 10abca7d35..c81da06682 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -39,7 +39,10 @@ PathSegment = | NameRef GenericArgList? | NameRef ParenthesizedArgList RetType? | NameRef ReturnTypeSyntax -| '<' Type ('as' PathType)? '>' +| TypeAnchor + +TypeAnchor = + '<' Type ('as' PathType)? '>' ReturnTypeSyntax = '(' '..' ')' @@ -98,7 +101,7 @@ WhereClause = 'where' predicates:(WherePred (',' WherePred)* ','?) WherePred = - ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? + ('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? //*************************// diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index cd9f4dba89..04c7e8a578 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -1232,21 +1232,13 @@ impl PathSegment { support::child(&self.syntax) } #[inline] - pub fn path_type(&self) -> Option { support::child(&self.syntax) } - #[inline] pub fn ret_type(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn return_type_syntax(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn ty(&self) -> Option { support::child(&self.syntax) } + pub fn type_anchor(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn coloncolon_token(&self) -> Option { support::token(&self.syntax, T![::]) } - #[inline] - pub fn l_angle_token(&self) -> Option { support::token(&self.syntax, T![<]) } - #[inline] - pub fn r_angle_token(&self) -> Option { support::token(&self.syntax, T![>]) } - #[inline] - pub fn as_token(&self) -> Option { support::token(&self.syntax, T![as]) } } pub struct PathType { pub(crate) syntax: SyntaxNode, @@ -1739,6 +1731,21 @@ impl TypeAlias { #[inline] pub fn type_token(&self) -> Option { support::token(&self.syntax, T![type]) } } +pub struct TypeAnchor { + pub(crate) syntax: SyntaxNode, +} +impl TypeAnchor { + #[inline] + pub fn path_type(&self) -> Option { support::child(&self.syntax) } + #[inline] + pub fn ty(&self) -> Option { support::child(&self.syntax) } + #[inline] + pub fn l_angle_token(&self) -> Option { support::token(&self.syntax, T![<]) } + #[inline] + pub fn r_angle_token(&self) -> Option { support::token(&self.syntax, T![>]) } + #[inline] + pub fn as_token(&self) -> Option { support::token(&self.syntax, T![as]) } +} pub struct TypeArg { pub(crate) syntax: SyntaxNode, } @@ -7108,6 +7115,42 @@ impl fmt::Debug for TypeAlias { 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 { + 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(&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 { #[inline] fn kind() -> SyntaxKind @@ -10624,6 +10667,11 @@ impl std::fmt::Display for TypeAlias { 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index b9ccd34cff..dcf853427e 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -276,18 +276,15 @@ impl ast::PathSegment { _ => PathSegmentKind::Name(name_ref), } } else { - match self.syntax().first_child_or_token()?.kind() { - T![<] => { - // or - // T is any TypeRef, Trait has to be a PathType - let mut type_refs = - self.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); - let type_ref = type_refs.next().and_then(ast::Type::cast); - let trait_ref = type_refs.next().and_then(ast::PathType::cast); - PathSegmentKind::Type { type_ref, trait_ref } - } - _ => return None, - } + let anchor = self.type_anchor()?; + // FIXME: Move this over to `ast::TypeAnchor` + // or + // T is any TypeRef, Trait has to be a PathType + let mut type_refs = + anchor.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); + let type_ref = type_refs.next().and_then(ast::Type::cast); + let trait_ref = type_refs.next().and_then(ast::PathType::cast); + PathSegmentKind::Type { type_ref, trait_ref } }; Some(res) } @@ -473,7 +470,7 @@ impl ast::Impl { // [#15778](https://github.com/rust-lang/rust-analyzer/issues/15778) impl ast::PathSegment { pub fn qualifying_trait(&self) -> Option { - let mut path_types = support::children(self.syntax()); + let mut path_types = support::children(self.type_anchor()?.syntax()); let first = path_types.next()?; path_types.next().or(Some(first)) } From b9733810bb7906a65feffe9315757c48a721234a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Jun 2025 11:30:43 +0200 Subject: [PATCH 3/3] feat: Add `dyn` keyword inlay hints --- crates/ide/src/inlay_hints.rs | 9 +- .../ide/src/inlay_hints/implied_dyn_trait.rs | 133 ++++++++++++++++++ crates/ide/src/inlay_hints/lifetime.rs | 2 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 crates/ide/src/inlay_hints/implied_dyn_trait.rs diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index d05a36c5f4..b094b09846 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -34,6 +34,7 @@ mod extern_block; mod generic_param; mod implicit_drop; mod implicit_static; +mod implied_dyn_trait; mod lifetime; mod param_name; mod range_exclusive; @@ -275,7 +276,12 @@ fn hints( ast::Type(ty) => match ty { 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, 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(()), @@ -445,6 +451,7 @@ pub enum InlayKind { Parameter, GenericParameter, Type, + Dyn, Drop, RangeExclusive, ExternUnsafety, diff --git a/crates/ide/src/inlay_hints/implied_dyn_trait.rs b/crates/ide/src/inlay_hints/implied_dyn_trait.rs new file mode 100644 index 0000000000..32d130503a --- /dev/null +++ b/crates/ide/src/inlay_hints/implied_dyn_trait.rs @@ -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, + FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig, + path: Either, +) -> 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 + ) {} + "#]], + ); + } +} diff --git a/crates/ide/src/inlay_hints/lifetime.rs b/crates/ide/src/inlay_hints/lifetime.rs index 939fe02691..0069452e7b 100644 --- a/crates/ide/src/inlay_hints/lifetime.rs +++ b/crates/ide/src/inlay_hints/lifetime.rs @@ -135,7 +135,7 @@ pub(super) fn fn_path_hints( ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, config: &InlayHintsConfig, - func: ast::PathType, + func: &ast::PathType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { return None;