Add expression fill mode variant for filling with underscore expressions

This commit is contained in:
Lukas Wirth 2025-04-28 10:39:36 +02:00
parent 8fb2dcc947
commit 7e526b6be7
31 changed files with 172 additions and 125 deletions

View file

@ -5,9 +5,13 @@ use hir::{
sym,
};
use ide_db::{
FxHashMap, assists::Assist, famous_defs::FamousDefs,
imports::import_assets::item_for_path_search, source_change::SourceChange,
syntax_helpers::tree_diff::diff, text_edit::TextEdit,
FxHashMap,
assists::{Assist, ExprFillDefaultMode},
famous_defs::FamousDefs,
imports::import_assets::item_for_path_search,
source_change::SourceChange,
syntax_helpers::tree_diff::diff,
text_edit::TextEdit,
use_trivial_constructor::use_trivial_constructor,
};
use stdx::format_to;
@ -102,8 +106,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
});
let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
crate::ExprFillDefaultMode::Todo => make::ext::expr_todo(),
crate::ExprFillDefaultMode::Default => {
ExprFillDefaultMode::Todo => make::ext::expr_todo(),
ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
ExprFillDefaultMode::Default => {
get_default_constructor(ctx, d, ty).unwrap_or_else(make::ext::expr_todo)
}
};

View file

@ -1,3 +1,5 @@
use std::ops::Not;
use hir::{
ClosureStyle, HirDisplay, ImportPathConfig,
db::ExpandDatabase,
@ -60,9 +62,13 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
let mut formatter = |_: &hir::Type| String::from("_");
let assists: Vec<Assist> = paths
let assists: Vec<Assist> = d
.expected
.is_unknown()
.not()
.then(|| "todo!()".to_owned())
.into_iter()
.filter_map(|path| {
.chain(paths.into_iter().filter_map(|path| {
path.gen_source_code(
&scope,
&mut formatter,
@ -75,7 +81,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
ctx.display_target,
)
.ok()
})
}))
.unique()
.map(|code| Assist {
id: AssistId::quick_fix("typed-hole"),
@ -95,9 +101,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
#[cfg(test)]
mod tests {
use crate::tests::{
check_diagnostics, check_fixes_unordered, check_has_fix, check_has_single_fix,
};
use crate::tests::{check_diagnostics, check_fixes_unordered, check_has_fix};
#[test]
fn unknown() {
@ -119,9 +123,9 @@ fn main() {
if _ {}
//^ 💡 error: invalid `_` expression, expected type `bool`
let _: fn() -> i32 = _;
//^ error: invalid `_` expression, expected type `fn() -> i32`
//^ 💡 error: invalid `_` expression, expected type `fn() -> i32`
let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion*
//^ error: invalid `_` expression, expected type `fn()`
//^ 💡 error: invalid `_` expression, expected type `fn()`
}
"#,
);
@ -147,7 +151,7 @@ fn main() {
fn main() {
let mut x = t();
x = _;
//^ error: invalid `_` expression, expected type `&str`
//^ 💡 error: invalid `_` expression, expected type `&str`
x = "";
}
fn t<T>() -> T { loop {} }
@ -308,7 +312,7 @@ fn main() {
#[test]
fn ignore_impl_func_with_incorrect_return() {
check_has_single_fix(
check_fixes_unordered(
r#"
struct Bar {}
trait Foo {
@ -323,7 +327,8 @@ fn main() {
let a: i32 = 1;
let c: Bar = _$0;
}"#,
r#"
vec![
r#"
struct Bar {}
trait Foo {
type Res;
@ -337,6 +342,21 @@ fn main() {
let a: i32 = 1;
let c: Bar = Bar { };
}"#,
r#"
struct Bar {}
trait Foo {
type Res;
fn foo(&self) -> Self::Res;
}
impl Foo for i32 {
type Res = Self;
fn foo(&self) -> Self::Res { 1 }
}
fn main() {
let a: i32 = 1;
let c: Bar = todo!();
}"#,
],
);
}

View file

@ -92,7 +92,7 @@ use hir::{
};
use ide_db::{
EditionedFileId, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, Severity, SnippetCap,
assists::{Assist, AssistId, AssistResolveStrategy},
assists::{Assist, AssistId, AssistResolveStrategy, ExprFillDefaultMode},
base_db::{ReleaseChannel, RootQueryDb as _},
generated::lints::{CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS, DEFAULT_LINTS, Lint, LintGroup},
imports::insert_use::InsertUseConfig,
@ -219,17 +219,6 @@ impl Diagnostic {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExprFillDefaultMode {
Todo,
Default,
}
impl Default for ExprFillDefaultMode {
fn default() -> Self {
Self::Todo
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticsConfig {
/// Whether native diagnostics are enabled.

View file

@ -3,14 +3,16 @@
mod overly_long_real_world_cases;
use ide_db::{
LineIndexDatabase, RootDatabase, assists::AssistResolveStrategy, base_db::SourceDatabase,
LineIndexDatabase, RootDatabase,
assists::{AssistResolveStrategy, ExprFillDefaultMode},
base_db::SourceDatabase,
};
use itertools::Itertools;
use stdx::trim_indent;
use test_fixture::WithFixture;
use test_utils::{MiniCore, assert_eq_text, extract_annotations};
use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
use crate::{DiagnosticsConfig, Severity};
/// Takes a multi-file input fixture with annotated cursor positions,
/// and checks that:
@ -160,55 +162,6 @@ pub(crate) fn check_has_fix(
assert!(fix.is_some(), "no diagnostic with desired fix");
}
#[track_caller]
pub(crate) fn check_has_single_fix(
#[rust_analyzer::rust_fixture] ra_fixture_before: &str,
#[rust_analyzer::rust_fixture] ra_fixture_after: &str,
) {
let after = trim_indent(ra_fixture_after);
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
let mut conf = DiagnosticsConfig::test_sample();
conf.expr_fill_default = ExprFillDefaultMode::Default;
let mut n_fixes = 0;
let fix = super::full_diagnostics(
&db,
&conf,
&AssistResolveStrategy::All,
file_position.file_id.file_id(&db),
)
.into_iter()
.find(|d| {
d.fixes
.as_ref()
.and_then(|fixes| {
n_fixes += fixes.len();
fixes.iter().find(|fix| {
if !fix.target.contains_inclusive(file_position.offset) {
return false;
}
let actual = {
let source_change = fix.source_change.as_ref().unwrap();
let file_id = *source_change.source_file_edits.keys().next().unwrap();
let mut actual = db.file_text(file_id).text(&db).to_string();
for (edit, snippet_edit) in source_change.source_file_edits.values() {
edit.apply(&mut actual);
if let Some(snippet_edit) = snippet_edit {
snippet_edit.apply(&mut actual);
}
}
actual
};
after == actual
})
})
.is_some()
});
assert!(fix.is_some(), "no diagnostic with desired fix");
assert!(n_fixes == 1, "Too many fixes suggested");
}
/// Checks that there's a diagnostic *without* fix at `$0`.
pub(crate) fn check_no_fix(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
let (db, file_position) = RootDatabase::with_position(ra_fixture);