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-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 0bd9d1a23aef..b75e7d4802d0 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,15 +1,18 @@ +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, edit::{AstNodeEdit, IndentLevel}, }, + hacks::parse_expr_from_str, syntax_editor::{Element, Position}, }; @@ -92,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) = 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()); @@ -111,14 +114,14 @@ 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) + (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) + (to_extract.clone()..=to_extract, expr, false) }; let place = match to_replace.start() { NodeOrToken::Node(node) => node.clone(), @@ -217,6 +220,11 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - editor.add_annotation(pat_name.syntax().clone(), tabstop); } + let to_extract_no_ref = if use_source_expr { + source_expr(ctx, to_replace.clone()).unwrap() + } 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) @@ -331,6 +339,15 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr { expr } +fn source_expr( + ctx: &AssistContext<'_, '_>, + range: RangeInclusive, +) -> Option { + 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()) +} + /// 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 { @@ -2828,7 +2845,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#" @@ -2850,7 +2866,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3; + let $0var_name = 2 + 3; let x = foo!(= var_name + 4); } "#, @@ -2878,7 +2894,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3; + let $0var_name = 2 + 3; let x = foo!(= var_name + 4); } "#, @@ -2906,7 +2922,7 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3+4; + let $0var_name = 2 + 3 + 4; let x = foo!(= var_name); } "#, @@ -2937,11 +2953,39 @@ macro_rules! foo { } fn main() { - let $0var_name = 2+3+4; + let $0var_name = 2 + 3 + 4; let x = foo!(= { 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", ); @@ -2970,7 +3014,7 @@ macro_rules! foo { } fn main() { - let $0x = 2+3; + let $0x = 2 + 3; let x = foo!(= Foo { x: x }); } "#, @@ -2998,7 +3042,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 +3050,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( 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#" 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( 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,