From a8192442518993ae0fa9805468e0ae1e8baeb714 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 14 Jul 2026 13:37:30 +0200 Subject: [PATCH 1/6] Fix invalid `pattern_matching_variant` lowering due to recovery --- crates/hir-ty/src/mir/lower/pattern_matching.rs | 8 ++++++++ crates/hir-ty/src/mir/lower/tests.rs | 16 ++++++++++++++++ .../src/handlers/unused_variables.rs | 13 ------------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 66b51a0e95b5..bd1ad70fe6a1 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -602,6 +602,14 @@ impl<'db> MirLowerCtx<'_, 'db> { shape: AdtPatternShape<'_>, mode: MatchingMode, ) -> Result<'db, (BasicBlockId, Option)> { + let place_ty = cond_place.ty(&self.result, &self.infcx, self.env).ty; + let Some((place_adt, _)) = place_ty.as_adt() else { + return Err(MirLowerError::TypeError("non ADT type matched with ADT pattern")); + }; + if place_adt != variant.adt_id(self.db) { + return Err(MirLowerError::TypeError("ADT pattern does not match place type")); + } + Ok(match variant { VariantId::EnumVariantId(v) => { if mode == MatchingMode::Check { diff --git a/crates/hir-ty/src/mir/lower/tests.rs b/crates/hir-ty/src/mir/lower/tests.rs index d8f7d549d6bf..cd495493885b 100644 --- a/crates/hir-ty/src/mir/lower/tests.rs +++ b/crates/hir-ty/src/mir/lower/tests.rs @@ -134,3 +134,19 @@ fn alias(x: T::A) { "#, ); } + +#[test] +fn borrowck_opaque_downcast_recovery_does_not_panic() { + check_borrowck( + r#" +//- minicore: option, sized +struct PathBuf; +fn opaque(path: T) -> impl Sized { + Some(path) +} +fn caller(path: &PathBuf) { + let Some(value) = opaque(path) else { return }; +} + "#, + ); +} diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs index afc74445f429..dede3b4aad5a 100644 --- a/crates/ide-diagnostics/src/handlers/unused_variables.rs +++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs @@ -378,19 +378,6 @@ fn main() { ); } - // regression test as we used to panic in this scenario - #[test] - fn unknown_struct_pattern_param_type() { - check_diagnostics( - r#" -struct S { field : u32 } -fn f(S { field }: error) { - // ^^^^^ 💡 warn: unused variable -} -"#, - ); - } - #[test] fn crate_attrs_lint_smoke_test() { check_diagnostics( From 763669f989430f40c19b9f4593e487d97641019b Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 13 Jul 2026 14:31:52 +0100 Subject: [PATCH 2/6] fix: Use quote! inside ast::make::expr_call() It's possible to make rust-analyzer panic because of `expr_from_text` not roundtripping. Prefer `quote!`, which is better anyway (less work, more robust). This crash only seems to occur in rare circumstances, so I haven't written a regression test. For reference, you can replicate it with the following code: mod serde { pub struct Serializer; pub trait Serialize { fn serialize(&self, s: Serializer); } } struct Duration; impl serde::Serialize for Duration { fn serialize(&self, s: serde::Serializer) {} } struct S { map: ::serialize(&self.map, s)`. Due to the extra leading `<`, we'd parse it as `<<`, which isn't valid in an expression (left shift is an infix operator). Since we were passing invalid Rust through the syntax builder, which only allows valid Rust code, we then panicked. 18: 0x104007fdc - >::unwrap at /rustc/2d8144b7880597b6e6d3dfd63a9a9efae3f533d3/library/core/src/option.rs:1013:21 19: 0x104007fdc - >::tree at /Users/wilfred/src/rust-analyzer/crates/syntax/src/lib.rs:120:37 20: 0x104006014 - syntax[8d95e7771a925714]::ast::make::expr_from_text_with_edition:: at /Users/wilfred/src/rust-analyzer/crates/syntax/src/ast/make.rs:1360:28 21: 0x103ff9d80 - syntax[8d95e7771a925714]::ast::make::expr_from_text:: at /Users/wilfred/src/rust-analyzer/crates/syntax/src/ast/make.rs:1354:5 22: 0x103f8e298 - syntax[8d95e7771a925714]::ast::make::expr_call at /Users/wilfred/src/rust-analyzer/crates/syntax/src/ast/make.rs:689:5 23: 0x103fed920 - ::expr_call at /Users/wilfred/src/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs:1157:19 24: 0x1027feb9c - ide_assists[84949edff2a7fc81]::handlers::generate_delegate_trait::func_assoc_item at /Users/wilfred/src/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs:796:22 25: 0x102800120 - ide_assists[84949edff2a7fc81]::handlers::generate_delegate_trait::process_assoc_item AI disclosure: Written with help by GPT-5.5. --- crates/syntax/src/ast/make.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index b0e59ff15953..16fbf248ecb5 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -686,7 +686,12 @@ pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::PrefixExpr { expr_from_text(&format!("{token}{expr}")) } pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::CallExpr { - expr_from_text(&format!("{f}{arg_list}")) + quote! { + CallExpr { + #f + #arg_list + } + } } pub fn expr_method_call( receiver: ast::Expr, From 7cc09005f584ccaeb5b93733b94aed6ef77d1e52 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Tue, 14 Jul 2026 22:57:00 +0800 Subject: [PATCH 3/6] Fix extract variable preserving whitespace from macro input Signed-off-by: cuishuang --- .../src/handlers/extract_variable.rs | 101 ++++++++++++++++-- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 0bd9d1a23aef..da8c5bc48d07 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,4 +1,4 @@ -use hir::{HirDisplay, TypeInfo}; +use hir::{HirDisplay, Module, TypeInfo}; use ide_db::{ assists::GroupLabel, syntax_helpers::{LexedStr, suggest_name}, @@ -10,6 +10,7 @@ use syntax::{ self, AstNode, edit::{AstNodeEdit, IndentLevel}, }, + hacks::parse_expr_from_str, syntax_editor::{Element, Position}, }; @@ -92,7 +93,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?; let range = node.text_range(); - let (to_replace, analysis) = if node.kind() == SyntaxKind::TOKEN_TREE { + let (to_replace, analysis, original_expr) = if node.kind() == SyntaxKind::TOKEN_TREE { let (first, last) = extract_token_range_of(&node, ctx.selection_trimmed())?; let first_descend = ctx.sema.descend_into_macros_single_exact(first.clone()); @@ -111,14 +112,15 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - if !node.text_range().contains_range(original_range.range) { return None; } - (cover_edit_range(&node, original_range.range), expr) + let original_expr = original_expr(ctx, original_range.file_id, original_range.range); + (cover_edit_range(&node, original_range.range), expr, original_expr) } else { let expr = node .descendants() .take_while(|it| range.contains_range(it.text_range())) .find_map(valid_target_expr(ctx))?; let to_extract = expr.syntax().syntax_element(); - (to_extract.clone()..=to_extract, expr) + (to_extract.clone()..=to_extract, expr, None) }; let place = match to_replace.start() { NodeOrToken::Node(node) => node.clone(), @@ -217,6 +219,10 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - editor.add_annotation(pat_name.syntax().clone(), tabstop); } + let to_extract_no_ref = original_expr + .clone() + .map(peel_parens) + .unwrap_or_else(|| prettify_macro_expr(ctx, module, to_extract_no_ref.clone())); let initializer = match ty.as_ref().filter(|_| needs_ref) { Some(receiver_type) if receiver_type.is_mutable_reference() => { make.expr_ref(to_extract_no_ref.clone(), true) @@ -331,6 +337,36 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr { expr } +fn original_expr( + ctx: &AssistContext<'_, '_>, + file_id: hir::EditionedFileId, + range: TextRange, +) -> Option { + if file_id != ctx.file_id() { + return None; + } + + let text = ctx.source_file().syntax().text().slice(range).to_string(); + parse_expr_from_str(&text, ctx.edition()) +} + +fn prettify_macro_expr(ctx: &AssistContext<'_, '_>, module: Module, expr: ast::Expr) -> ast::Expr { + let Some(scope) = ctx.sema.scope(expr.syntax()) else { + return expr; + }; + let Some(macro_file) = scope.file_id().macro_file() else { + return expr; + }; + + let prettified = hir::prettify_macro_expansion( + ctx.db(), + expr.syntax().clone(), + macro_file.expansion_span_map(ctx.db()), + module.krate(ctx.db()).into(), + ); + ast::Expr::cast(prettified).unwrap_or(expr) +} + /// Check whether the node is a valid expression which can be extracted to a variable. /// In general that's true for any expression, but in some cases that would produce invalid code. fn valid_target_expr(ctx: &AssistContext<'_, '_>) -> impl Fn(SyntaxNode) -> Option { @@ -2850,7 +2886,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3; + let $0var_name = 2 + 3; let x = foo!(= var_name + 4); } "#, @@ -2878,7 +2914,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3; + let $0var_name = 2 + 3; let x = foo!(= var_name + 4); } "#, @@ -2906,7 +2942,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3+4; + let $0var_name = 2 + 3 + 4; let x = foo!(= var_name); } "#, @@ -2937,7 +2973,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3+4; + let $0var_name = 2 + 3 + 4; let x = foo!(= { var_name }); @@ -2970,7 +3006,7 @@ macro_rules! foo { } fn main() { - let $0x = 2+3; + let $0x = 2 + 3; let x = foo!(= Foo { x: x }); } "#, @@ -2998,7 +3034,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3; + let $0var_name = 2 + 3; let x = foo!(= Foo { x: var_name + 4 }); } "#, @@ -3006,6 +3042,51 @@ fn main() { ); } + #[test] + fn extract_variable_in_assert_macro_preserves_required_whitespace() { + check_assist_by_label( + extract_variable, + r#" +//- minicore: assert +fn check(value: &mut usize) -> bool { + false +} + +fn foo(mut bar: usize) { + assert!(check($0&mut bar$0)); +} +"#, + r#" +fn check(value: &mut usize) -> bool { + false +} + +fn foo(mut bar: usize) { + let $0value = &mut bar; + assert!(check(value)); +} +"#, + "Extract into variable", + ); + + check_assist_by_label( + extract_variable, + r#" +//- minicore: assert +fn main() { + assert!($0if true {true} else {false}$0); +} +"#, + r#" +fn main() { + let $0var_name = if true {true} else {false}; + assert!(var_name); +} +"#, + "Extract into variable", + ); + } + #[test] fn regression_22441() { check_assist_by_label( From a2775d068f3962b8a4ec8569d9d90ff0f91de27e Mon Sep 17 00:00:00 2001 From: cuishuang Date: Tue, 14 Jul 2026 23:48:36 +0800 Subject: [PATCH 4/6] Fix extract variable preserving whitespace from macro input Signed-off-by: cuishuang --- .../src/handlers/extract_variable.rs | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index da8c5bc48d07..1c03751e623a 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,10 +1,12 @@ -use hir::{HirDisplay, Module, TypeInfo}; +use std::ops::RangeInclusive; + +use hir::{HirDisplay, TypeInfo}; use ide_db::{ assists::GroupLabel, syntax_helpers::{LexedStr, suggest_name}, }; use syntax::{ - Direction, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, T, TextRange, + Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, TextRange, algo::{ancestors_at_offset, skip_trivia_token}, ast::{ self, AstNode, @@ -93,7 +95,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?; let range = node.text_range(); - let (to_replace, analysis, original_expr) = if node.kind() == SyntaxKind::TOKEN_TREE { + let (to_replace, analysis, prefer_source_expr) = if node.kind() == SyntaxKind::TOKEN_TREE { let (first, last) = extract_token_range_of(&node, ctx.selection_trimmed())?; let first_descend = ctx.sema.descend_into_macros_single_exact(first.clone()); @@ -112,15 +114,14 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - if !node.text_range().contains_range(original_range.range) { return None; } - let original_expr = original_expr(ctx, original_range.file_id, original_range.range); - (cover_edit_range(&node, original_range.range), expr, original_expr) + (cover_edit_range(&node, original_range.range), expr, true) } else { let expr = node .descendants() .take_while(|it| range.contains_range(it.text_range())) .find_map(valid_target_expr(ctx))?; let to_extract = expr.syntax().syntax_element(); - (to_extract.clone()..=to_extract, expr, None) + (to_extract.clone()..=to_extract, expr, false) }; let place = match to_replace.start() { NodeOrToken::Node(node) => node.clone(), @@ -219,10 +220,12 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - editor.add_annotation(pat_name.syntax().clone(), tabstop); } - let to_extract_no_ref = original_expr - .clone() - .map(peel_parens) - .unwrap_or_else(|| prettify_macro_expr(ctx, module, to_extract_no_ref.clone())); + let to_extract_no_ref = if prefer_source_expr { + source_expr(ctx, to_replace.clone()) + .unwrap_or_else(|| to_extract_no_ref.clone()) + } else { + to_extract_no_ref.clone() + }; let initializer = match ty.as_ref().filter(|_| needs_ref) { Some(receiver_type) if receiver_type.is_mutable_reference() => { make.expr_ref(to_extract_no_ref.clone(), true) @@ -337,36 +340,15 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr { expr } -fn original_expr( +fn source_expr( ctx: &AssistContext<'_, '_>, - file_id: hir::EditionedFileId, - range: TextRange, + range: RangeInclusive, ) -> Option { - if file_id != ctx.file_id() { - return None; - } - + let range = range.start().text_range().cover(range.end().text_range()); let text = ctx.source_file().syntax().text().slice(range).to_string(); parse_expr_from_str(&text, ctx.edition()) } -fn prettify_macro_expr(ctx: &AssistContext<'_, '_>, module: Module, expr: ast::Expr) -> ast::Expr { - let Some(scope) = ctx.sema.scope(expr.syntax()) else { - return expr; - }; - let Some(macro_file) = scope.file_id().macro_file() else { - return expr; - }; - - let prettified = hir::prettify_macro_expansion( - ctx.db(), - expr.syntax().clone(), - macro_file.expansion_span_map(ctx.db()), - module.krate(ctx.db()).into(), - ); - ast::Expr::cast(prettified).unwrap_or(expr) -} - /// Check whether the node is a valid expression which can be extracted to a variable. /// In general that's true for any expression, but in some cases that would produce invalid code. fn valid_target_expr(ctx: &AssistContext<'_, '_>) -> impl Fn(SyntaxNode) -> Option { @@ -2864,7 +2846,6 @@ fn main() { #[test] fn extract_variable_in_token_tree() { - // FIXME: Keep the original trivia instead of extracting macro expanded? check_assist_by_label( extract_variable, r#" @@ -2978,6 +2959,34 @@ fn main() { var_name }); } +"#, + "Extract into variable", + ); + + check_assist_by_label( + extract_variable, + r#" +macro_rules! identity { + ($e:expr) => { + $e + }; +} + +fn main() { + let x = identity!($0(1+2)$0); +} +"#, + r#" +macro_rules! identity { + ($e:expr) => { + $e + }; +} + +fn main() { + let $0var_name = (1+2); + let x = identity!(var_name); +} "#, "Extract into variable", ); From 38b37c1f38f94a02098da6d4694b30e544a4ae69 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 15 Jul 2026 00:37:07 +0800 Subject: [PATCH 5/6] Fix extract variable preserving whitespace from macro input Signed-off-by: cuishuang --- crates/ide-assists/src/handlers/extract_variable.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 1c03751e623a..b75e7d4802d0 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -95,7 +95,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?; let range = node.text_range(); - let (to_replace, analysis, prefer_source_expr) = if node.kind() == SyntaxKind::TOKEN_TREE { + let (to_replace, analysis, use_source_expr) = if node.kind() == SyntaxKind::TOKEN_TREE { let (first, last) = extract_token_range_of(&node, ctx.selection_trimmed())?; let first_descend = ctx.sema.descend_into_macros_single_exact(first.clone()); @@ -220,9 +220,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - editor.add_annotation(pat_name.syntax().clone(), tabstop); } - let to_extract_no_ref = if prefer_source_expr { - source_expr(ctx, to_replace.clone()) - .unwrap_or_else(|| to_extract_no_ref.clone()) + let to_extract_no_ref = if use_source_expr { + source_expr(ctx, to_replace.clone()).unwrap() } else { to_extract_no_ref.clone() }; From a3aa30fa410eb492ed32163ad431d36200e3a51d Mon Sep 17 00:00:00 2001 From: Michael Ingley Date: Tue, 14 Jul 2026 11:47:13 -0500 Subject: [PATCH 6/6] tidy: Reuse flyimport lowercase buffer Reuse lowercase storage across fuzzy ordering comparisons while preserving the existing linear substring search. Add completion coverage for case-insensitive ranking and path tie-breaking. AI usage: GitHub Copilot CLI assisted with implementation and validation. --- .../src/completions/flyimport.rs | 43 +++++++++++++------ crates/ide-completion/src/tests/flyimport.rs | 28 ++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index b350647b9a2b..972cc6d32fef 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -262,6 +262,7 @@ fn import_on_the_fly<'db>( } }; let user_input_lowercased = potential_import_name.to_lowercase(); + let mut import_name_buffer = String::new(); let import_cfg = ctx.config.import_path_config(); @@ -276,9 +277,13 @@ fn import_on_the_fly<'db>( }) .filter(|import| filter_excluded_flyimport(ctx, import)) .sorted_by(|a, b| { - let key = |import_path| { + let mut key = |import_path| { ( - compute_fuzzy_completion_order_key(import_path, &user_input_lowercased), + compute_fuzzy_completion_order_key( + import_path, + &user_input_lowercased, + &mut import_name_buffer, + ), import_path, ) }; @@ -310,6 +315,7 @@ fn import_on_the_fly_pat_<'db>( ItemInNs::Values(def) => matches!(def, hir::ModuleDef::Const(_)), }; let user_input_lowercased = potential_import_name.to_lowercase(); + let mut import_name_buffer = String::new(); let cfg = ctx.config.import_path_config(); import_assets @@ -322,9 +328,13 @@ fn import_on_the_fly_pat_<'db>( && ctx.check_stability(original_item.attrs(ctx.db).as_ref()) }) .sorted_by(|a, b| { - let key = |import_path| { + let mut key = |import_path| { ( - compute_fuzzy_completion_order_key(import_path, &user_input_lowercased), + compute_fuzzy_completion_order_key( + import_path, + &user_input_lowercased, + &mut import_name_buffer, + ), import_path, ) }; @@ -351,6 +361,7 @@ fn import_on_the_fly_method<'db>( ImportScope::find_insert_use_container(&position, &ctx.sema)?; let user_input_lowercased = potential_import_name.to_lowercase(); + let mut import_name_buffer = String::new(); let cfg = ctx.config.import_path_config(); @@ -362,9 +373,13 @@ fn import_on_the_fly_method<'db>( }) .filter(|import| filter_excluded_flyimport(ctx, import)) .sorted_by(|a, b| { - let key = |import_path| { + let mut key = |import_path| { ( - compute_fuzzy_completion_order_key(import_path, &user_input_lowercased), + compute_fuzzy_completion_order_key( + import_path, + &user_input_lowercased, + &mut import_name_buffer, + ), import_path, ) }; @@ -437,15 +452,15 @@ fn import_assets_for_path<'db>( fn compute_fuzzy_completion_order_key( proposed_mod_path: &hir::ModPath, user_input_lowercased: &str, + import_name_buffer: &mut String, ) -> usize { cov_mark::hit!(certain_fuzzy_order_test); - let import_name = match proposed_mod_path.segments().last() { - // FIXME: nasty alloc, this is a hot path! - Some(name) => name.as_str().to_ascii_lowercase(), - None => return usize::MAX, + let Some(import_name) = proposed_mod_path.segments().last() else { + return usize::MAX; }; - match import_name.match_indices(user_input_lowercased).next() { - Some((first_matching_index, _)) => first_matching_index, - None => usize::MAX, - } + + import_name_buffer.clear(); + import_name_buffer.push_str(import_name.as_str()); + import_name_buffer.make_ascii_lowercase(); + import_name_buffer.find(user_input_lowercased).unwrap_or(usize::MAX) } diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index dc162774a0a6..8a5025caf901 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs @@ -248,6 +248,34 @@ fn main() { ); } +#[test] +fn fuzzy_completion_order_is_case_insensitive_and_deterministic() { + check( + r#" +//- /lib.rs crate:dep +pub mod zed { + pub struct HIRThing; +} +pub mod alpha { + pub struct HIRThing; +} +pub struct BeforeHIRThing; +pub struct HiiirThing; + +//- /main.rs crate:main deps:dep +fn main() { + hir$0 +} +"#, + expect![[r#" + st HIRThing (use dep::alpha::HIRThing) HIRThing + st HIRThing (use dep::zed::HIRThing) HIRThing + st BeforeHIRThing (use dep::BeforeHIRThing) BeforeHIRThing + st HiiirThing (use dep::HiiirThing) HiiirThing + "#]], + ); +} + #[test] fn trait_function_fuzzy_completion() { let fixture = r#"