Merge pull request #20717 from ShoyuVanilla/migrate-more
internal: Migrate more predicate things to next-solver
diff --git a/crates/hir-ty/src/tests/regression/new_solver.rs b/crates/hir-ty/src/tests/regression/new_solver.rs
index ead79a8..c7711f3 100644
--- a/crates/hir-ty/src/tests/regression/new_solver.rs
+++ b/crates/hir-ty/src/tests/regression/new_solver.rs
@@ -418,3 +418,35 @@
"#]],
);
}
+
+#[test]
+fn regression_19637() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+pub trait Any {}
+
+impl<T: 'static> Any for T {}
+
+pub trait Trait: Any {
+ type F;
+}
+
+pub struct TT {}
+
+impl Trait for TT {
+ type F = f32;
+}
+
+pub fn coercion(x: &mut dyn Any) -> &mut dyn Any {
+ x
+}
+
+fn main() {
+ let mut t = TT {};
+ let tt = &mut t as &mut dyn Trait<F = f32>;
+ let st = coercion(tt);
+}
+ "#,
+ );
+}
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index 63a41ae..27755db 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -295,7 +295,7 @@
Some(field_expr) => Some({
let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
let new_field_name = field_names.get(&field_name)?;
- let new_expr = make.expr_path(ast::make::ext::ident_path(new_field_name));
+ let new_expr = ast::make::expr_path(ast::make::ext::ident_path(new_field_name));
// If struct binding is a reference, we might need to deref field usages
if data.is_ref {
@@ -305,7 +305,7 @@
ref_data.wrap_expr(new_expr).syntax().clone_for_update(),
)
} else {
- (field_expr.syntax().clone(), new_expr.syntax().clone())
+ (field_expr.syntax().clone(), new_expr.syntax().clone_for_update())
}
}),
None => Some((
@@ -698,6 +698,52 @@
}
#[test]
+ fn ref_not_add_parenthesis_and_deref_record() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ fn main() {
+ let $0foo = &Foo { bar: 1, baz: 2 };
+ let _ = &foo.bar;
+ }
+ "#,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ fn main() {
+ let Foo { bar, baz } = &Foo { bar: 1, baz: 2 };
+ let _ = bar;
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_not_add_parenthesis_and_deref_tuple() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo(i32, i32);
+
+ fn main() {
+ let $0foo = &Foo(1, 2);
+ let _ = &foo.0;
+ }
+ "#,
+ r#"
+ struct Foo(i32, i32);
+
+ fn main() {
+ let Foo(_0, _1) = &Foo(1, 2);
+ let _ = _0;
+ }
+ "#,
+ )
+ }
+
+ #[test]
fn record_struct_name_collision() {
check_assist(
destructure_struct_binding,
diff --git a/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs b/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
index 6198dbc..056edb0 100644
--- a/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
+++ b/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
@@ -39,6 +39,9 @@
cov_mark::hit!(test_gen_default_on_non_unit_variant_not_implemented);
return None;
}
+ if !variant.syntax().text_range().contains_range(ctx.selection_trimmed()) {
+ return None;
+ }
if existing_default_impl(&ctx.sema, &variant).is_some() {
cov_mark::hit!(test_gen_default_impl_already_exists);
@@ -115,6 +118,49 @@
}
#[test]
+ fn test_generate_default_selected_variant() {
+ check_assist(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant {
+ Undefined,
+ $0Minor$0,
+ Major,
+}
+"#,
+ r#"
+enum Variant {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Default for Variant {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_default_not_applicable_with_multiple_variant_selection() {
+ check_assist_not_applicable(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant {
+ Undefined,
+ $0Minor,
+ M$0ajor,
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_generate_default_already_implemented() {
cov_mark::check!(test_gen_default_impl_already_exists);
check_assist_not_applicable(
diff --git a/crates/ide-assists/src/handlers/remove_dbg.rs b/crates/ide-assists/src/handlers/remove_dbg.rs
index 414f674..08779a3 100644
--- a/crates/ide-assists/src/handlers/remove_dbg.rs
+++ b/crates/ide-assists/src/handlers/remove_dbg.rs
@@ -83,7 +83,9 @@
let input_expressions = input_expressions
.into_iter()
.filter_map(|(is_sep, group)| (!is_sep).then_some(group))
- .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT))
+ .map(|tokens| tokens.collect::<Vec<_>>())
+ .filter(|tokens| !tokens.iter().all(|it| it.kind().is_trivia()))
+ .map(|tokens| syntax::hacks::parse_expr_from_str(&tokens.iter().join(""), Edition::CURRENT))
.collect::<Option<Vec<ast::Expr>>>()?;
let parent = macro_expr.syntax().parent()?;
@@ -268,6 +270,8 @@
dbg!('x');
dbg!(&n);
dbg!(n);
+ dbg!(n,);
+ dbg!(n, );
// needless comment
dbg!("foo");$0
}
@@ -282,6 +286,17 @@
}
#[test]
+ fn test_remove_trailing_comma_dbg() {
+ check("$0dbg!(1 + 1,)", "1 + 1");
+ check("$0dbg!(1 + 1, )", "1 + 1");
+ check("$0dbg!(1 + 1,\n)", "1 + 1");
+ check("$0dbg!(1 + 1, 2 + 3)", "(1 + 1, 2 + 3)");
+ check("$0dbg!(1 + 1, 2 + 3 )", "(1 + 1, 2 + 3)");
+ check("$0dbg!(1 + 1, 2 + 3, )", "(1 + 1, 2 + 3)");
+ check("$0dbg!(1 + 1, 2 + 3 ,)", "(1 + 1, 2 + 3)");
+ }
+
+ #[test]
fn test_remove_dbg_not_applicable() {
check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}");
check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}");
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 7b78a97..cdb1ad8 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -637,6 +637,9 @@
.or_else(|| it.rhs().and_then(|rhs| sema.type_of_expr(&rhs)))
.map(TypeInfo::original);
(ty, None)
+ } else if let Some(ast::BinaryOp::LogicOp(_)) = it.op_kind() {
+ let ty = sema.type_of_expr(&it.clone().into()).map(TypeInfo::original);
+ (ty, None)
} else {
(None, None)
}
@@ -707,9 +710,13 @@
(ty, None)
},
ast::IfExpr(it) => {
- let ty = it.condition()
- .and_then(|e| sema.type_of_expr(&e))
- .map(TypeInfo::original);
+ let ty = if let Some(body) = it.then_branch()
+ && token.text_range().end() > body.syntax().text_range().start()
+ {
+ sema.type_of_expr(&body.into())
+ } else {
+ it.condition().and_then(|e| sema.type_of_expr(&e))
+ }.map(TypeInfo::original);
(ty, None)
},
ast::IdentPat(it) => {
diff --git a/crates/ide-completion/src/context/tests.rs b/crates/ide-completion/src/context/tests.rs
index 445afa7..d9ec791 100644
--- a/crates/ide-completion/src/context/tests.rs
+++ b/crates/ide-completion/src/context/tests.rs
@@ -279,6 +279,62 @@
}
#[test]
+fn expected_type_if_let_chain_bool() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let f = Foo::Quux;
+ if let c = f && $0 { }
+}
+"#,
+ expect![[r#"ty: bool, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_if_condition() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ if a$0 { }
+}
+"#,
+ expect![[r#"ty: bool, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_if_body() {
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let _: Foo = if true {
+ $0
+ };
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let _: Foo = if true {
+ Foo::Bar
+ } else {
+ $0
+ };
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
fn expected_type_fn_ret_without_leading_char() {
cov_mark::check!(expected_type_fn_ret_without_leading_char);
check_expected_type_and_name(
@@ -526,3 +582,16 @@
expect![[r#"ty: State, name: ?"#]],
);
}
+
+#[test]
+fn expected_type_logic_op() {
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ true && $0;
+}
+"#,
+ expect![[r#"ty: bool, name: ?"#]],
+ );
+}
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index c420953..98a6f95 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -271,8 +271,6 @@
sn macro_rules
sn pd
sn ppd
- ex false
- ex true
"#]],
)
}
@@ -1668,7 +1666,7 @@
fn foo() { let x = if foo {} $0 else {}; }
"#,
expect![[r#"
- fn foo fn()
+ fn foo() fn()
bt u32 u32
kw async
kw const
@@ -1710,7 +1708,7 @@
fn foo() { let x = if foo {} $0 else if true {}; }
"#,
expect![[r#"
- fn foo fn()
+ fn foo() fn()
bt u32 u32
kw async
kw const
@@ -1795,7 +1793,7 @@
fn foo() { let x = if foo {} $0 else if true {} else {}; }
"#,
expect![[r#"
- fn foo fn()
+ fn foo() fn()
bt u32 u32
kw async
kw const
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
index e6bbff0..a4f3ca0 100644
--- a/crates/ide-diagnostics/src/handlers/unused_variables.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -6,7 +6,7 @@
label::Label,
source_change::SourceChange,
};
-use syntax::{Edition, TextRange};
+use syntax::{AstNode, Edition, TextRange};
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
@@ -24,15 +24,21 @@
}
let diagnostic_range = ctx.sema.diagnostics_display_range(ast);
// The range for the Actual Name. We don't want to replace the entire declaration. Using the diagnostic range causes issues within in Array Destructuring.
- let name_range = d
- .local
- .primary_source(ctx.sema.db)
+ let primary_source = d.local.primary_source(ctx.sema.db);
+ let name_range = primary_source
.name()
.map(|v| v.syntax().original_file_range_rooted(ctx.sema.db))
.filter(|it| {
Some(it.file_id) == ast.file_id.file_id()
&& diagnostic_range.range.contains_range(it.range)
});
+ let is_shorthand_field = primary_source
+ .source
+ .value
+ .left()
+ .and_then(|name| name.syntax().parent())
+ .and_then(syntax::ast::RecordPatField::cast)
+ .is_some_and(|field| field.colon_token().is_none());
let var_name = d.local.name(ctx.sema.db);
Some(
Diagnostic::new_with_syntax_node_ptr(
@@ -48,6 +54,7 @@
it.range,
diagnostic_range,
ast.file_id.is_macro(),
+ is_shorthand_field,
ctx.edition,
)
})),
@@ -60,24 +67,23 @@
name_range: TextRange,
diagnostic_range: FileRange,
is_in_marco: bool,
+ is_shorthand_field: bool,
edition: Edition,
) -> Option<Vec<Assist>> {
if is_in_marco {
return None;
}
+ let name = var_name.display(db, edition);
+ let new_name = if is_shorthand_field { format!("{name}: _{name}") } else { format!("_{name}") };
Some(vec![Assist {
id: AssistId::quick_fix("unscore_unused_variable_name"),
- label: Label::new(format!(
- "Rename unused {} to _{}",
- var_name.display(db, edition),
- var_name.display(db, edition)
- )),
+ label: Label::new(format!("Rename unused {name} to {new_name}")),
group: None,
target: diagnostic_range.range,
source_change: Some(SourceChange::from_text_edit(
diagnostic_range.file_id,
- TextEdit::replace(name_range, format!("_{}", var_name.display(db, edition))),
+ TextEdit::replace(name_range, new_name),
)),
command: None,
}])
@@ -220,7 +226,7 @@
fn main() {
let f = Foo { f1: 0, f2: 0 };
match f {
- Foo { _f1, f2 } => {
+ Foo { f1: _f1, f2 } => {
_ = f2;
}
}
@@ -263,6 +269,46 @@
);
}
+ #[test]
+ fn unused_variable_in_record_field() {
+ check_fix(
+ r#"
+struct S { field : u32 }
+fn main() {
+ let s = S { field : 2 };
+ let S { field: $0x } = s
+}
+"#,
+ r#"
+struct S { field : u32 }
+fn main() {
+ let s = S { field : 2 };
+ let S { field: _x } = s
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unused_variable_in_shorthand_record_field() {
+ check_fix(
+ r#"
+struct S { field : u32 }
+fn main() {
+ let s = S { field : 2 };
+ let S { $0field } = s
+}
+"#,
+ r#"
+struct S { field : u32 }
+fn main() {
+ let s = S { field : 2 };
+ let S { field: _field } = s
+}
+"#,
+ );
+ }
+
// regression test as we used to panic in this scenario
#[test]
fn unknown_struct_pattern_param_type() {
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index 203173c..5eda5af 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -9,7 +9,7 @@
use std::{cell::RefCell, io, mem, process::Command};
use base_db::Env;
-use cargo_metadata::{Message, camino::Utf8Path};
+use cargo_metadata::{Message, PackageId, camino::Utf8Path};
use cfg::CfgAtom;
use itertools::Itertools;
use la_arena::ArenaMap;
@@ -18,6 +18,7 @@
use serde::Deserialize as _;
use stdx::never;
use toolchain::Tool;
+use triomphe::Arc;
use crate::{
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
@@ -284,7 +285,7 @@
// NB: Cargo.toml could have been modified between `cargo metadata` and
// `cargo check`. We shouldn't assume that package ids we see here are
// exactly those from `config`.
- let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
+ let mut by_id: FxHashMap<Arc<PackageId>, Package> = FxHashMap::default();
for package in workspace.packages() {
outputs.insert(package, BuildScriptOutput::default());
by_id.insert(workspace[package].id.clone(), package);
@@ -323,7 +324,7 @@
// ideally this would be something like:
// with_output_for: impl FnMut(&str, dyn FnOnce(&mut BuildScriptOutput)),
// but owned trait objects aren't a thing
- mut with_output_for: impl FnMut(&str, &mut dyn FnMut(&str, &mut BuildScriptOutput)),
+ mut with_output_for: impl FnMut(&PackageId, &mut dyn FnMut(&str, &mut BuildScriptOutput)),
progress: &dyn Fn(String),
) -> io::Result<Option<String>> {
let errors = RefCell::new(String::new());
@@ -346,7 +347,7 @@
match message {
Message::BuildScriptExecuted(mut message) => {
- with_output_for(&message.package_id.repr, &mut |name, data| {
+ with_output_for(&message.package_id, &mut |name, data| {
progress(format!("build script {name} run"));
let cfgs = {
let mut acc = Vec::new();
@@ -377,7 +378,7 @@
});
}
Message::CompilerArtifact(message) => {
- with_output_for(&message.package_id.repr, &mut |name, data| {
+ with_output_for(&message.package_id, &mut |name, data| {
progress(format!("proc-macro {name} built"));
if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index e613fd5..adc0cc5 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -5,7 +5,7 @@
use anyhow::Context;
use base_db::Env;
-use cargo_metadata::{CargoOpt, MetadataCommand};
+use cargo_metadata::{CargoOpt, MetadataCommand, PackageId};
use la_arena::{Arena, Idx};
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -14,6 +14,7 @@
use span::Edition;
use stdx::process::spawn_with_streaming_output;
use toolchain::Tool;
+use triomphe::Arc;
use crate::cargo_config_file::make_lockfile_copy;
use crate::{CfgOverrides, InvocationStrategy};
@@ -155,8 +156,8 @@
pub features: FxHashMap<String, Vec<String>>,
/// List of features enabled on this package
pub active_features: Vec<String>,
- /// String representation of package id
- pub id: String,
+ /// Package id
+ pub id: Arc<PackageId>,
/// Authors as given in the `Cargo.toml`
pub authors: Vec<String>,
/// Description as given in the `Cargo.toml`
@@ -173,6 +174,10 @@
pub rust_version: Option<semver::Version>,
/// The contents of [package.metadata.rust-analyzer]
pub metadata: RustAnalyzerPackageMetaData,
+ /// If this package is a member of the workspace, store all direct and transitive
+ /// dependencies as long as they are workspace members, to track dependency relationships
+ /// between members.
+ pub all_member_deps: Option<FxHashSet<Package>>,
}
#[derive(Deserialize, Default, Debug, Clone, Eq, PartialEq)]
@@ -334,6 +339,8 @@
let mut is_virtual_workspace = true;
let mut requires_rustc_private = false;
+ let mut members = FxHashSet::default();
+
meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
for meta_pkg in meta.packages {
let cargo_metadata::Package {
@@ -356,6 +363,7 @@
rust_version,
..
} = meta_pkg;
+ let id = Arc::new(id);
let meta = from_value::<PackageMetadata>(metadata).unwrap_or_default();
let edition = match edition {
cargo_metadata::Edition::E2015 => Edition::Edition2015,
@@ -375,7 +383,7 @@
let manifest = ManifestPath::try_from(AbsPathBuf::assert(manifest_path)).unwrap();
is_virtual_workspace &= manifest != ws_manifest_path;
let pkg = packages.alloc(PackageData {
- id: id.repr.clone(),
+ id: id.clone(),
name: name.to_string(),
version,
manifest: manifest.clone(),
@@ -395,7 +403,11 @@
features: features.into_iter().collect(),
active_features: Vec::new(),
metadata: meta.rust_analyzer.unwrap_or_default(),
+ all_member_deps: None,
});
+ if is_member {
+ members.insert(pkg);
+ }
let pkg_data = &mut packages[pkg];
requires_rustc_private |= pkg_data.metadata.rustc_private;
pkg_by_id.insert(id, pkg);
@@ -440,6 +452,43 @@
.extend(node.features.into_iter().map(|it| it.to_string()));
}
+ fn saturate_all_member_deps(
+ packages: &mut Arena<PackageData>,
+ to_visit: Package,
+ visited: &mut FxHashSet<Package>,
+ members: &FxHashSet<Package>,
+ ) {
+ let pkg_data = &mut packages[to_visit];
+
+ if !visited.insert(to_visit) {
+ return;
+ }
+
+ let deps: Vec<_> = pkg_data
+ .dependencies
+ .iter()
+ .filter_map(|dep| {
+ let pkg = dep.pkg;
+ if members.contains(&pkg) { Some(pkg) } else { None }
+ })
+ .collect();
+
+ let mut all_member_deps = FxHashSet::from_iter(deps.iter().copied());
+ for dep in deps {
+ saturate_all_member_deps(packages, dep, visited, members);
+ if let Some(transitives) = &packages[dep].all_member_deps {
+ all_member_deps.extend(transitives);
+ }
+ }
+
+ packages[to_visit].all_member_deps = Some(all_member_deps);
+ }
+
+ let mut visited = FxHashSet::default();
+ for member in members.iter() {
+ saturate_all_member_deps(&mut packages, *member, &mut visited, &members);
+ }
+
CargoWorkspace {
packages,
targets,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6b489d5..a88d228 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -267,6 +267,8 @@
inlayHints_lifetimeElisionHints_useParameterNames: bool = false,
/// Maximum length for inlay hints. Set to null to have an unlimited length.
+ ///
+ /// **Note:** This is mostly a hint, and we don't guarantee to strictly follow the limit.
inlayHints_maxLength: Option<usize> = Some(25),
/// Show function parameter name inlay hints at the call site.
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index ee50237..4bfad98 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -120,6 +120,29 @@
}
}
+ pub(crate) fn clear_check_older_than_for_package(
+ &mut self,
+ flycheck_id: usize,
+ package_id: Arc<PackageId>,
+ generation: DiagnosticsGeneration,
+ ) {
+ let Some(check) = self.check.get_mut(flycheck_id) else {
+ return;
+ };
+ let package_id = Some(package_id);
+ let Some((_, checks)) = check
+ .per_package
+ .extract_if(|k, v| *k == package_id && v.generation < generation)
+ .next()
+ else {
+ return;
+ };
+ self.changes.extend(checks.per_file.into_keys());
+ if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
+ fixes.remove(&package_id);
+ }
+ }
+
pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
self.native_syntax.remove(&file_id);
self.native_semantic.remove(&file_id);
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index 315c45d..cded34b 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -180,17 +180,27 @@
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
self.sender
- .send(StateChange::Restart { generation, package: None, saved_file, target: None })
+ .send(StateChange::Restart {
+ generation,
+ scope: FlycheckScope::Workspace,
+ saved_file,
+ target: None,
+ })
.unwrap();
}
/// Schedule a re-start of the cargo check worker to do a package wide check.
- pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) {
+ pub(crate) fn restart_for_package(
+ &self,
+ package: Arc<PackageId>,
+ target: Option<Target>,
+ workspace_deps: Option<FxHashSet<Arc<PackageId>>>,
+ ) {
let generation = self.generation.fetch_add(1, Ordering::Relaxed) + 1;
self.sender
.send(StateChange::Restart {
generation,
- package: Some(package),
+ scope: FlycheckScope::Package { package, workspace_deps },
saved_file: None,
target,
})
@@ -213,8 +223,13 @@
#[derive(Debug)]
pub(crate) enum ClearDiagnosticsKind {
- All,
- OlderThan(DiagnosticsGeneration),
+ All(ClearScope),
+ OlderThan(DiagnosticsGeneration, ClearScope),
+}
+
+#[derive(Debug)]
+pub(crate) enum ClearScope {
+ Workspace,
Package(Arc<PackageId>),
}
@@ -275,10 +290,15 @@
DidFailToRestart(String),
}
+enum FlycheckScope {
+ Workspace,
+ Package { package: Arc<PackageId>, workspace_deps: Option<FxHashSet<Arc<PackageId>>> },
+}
+
enum StateChange {
Restart {
generation: DiagnosticsGeneration,
- package: Option<String>,
+ scope: FlycheckScope,
saved_file: Option<AbsPathBuf>,
target: Option<Target>,
},
@@ -298,6 +318,7 @@
/// or the project root of the project.
root: Arc<AbsPathBuf>,
sysroot_root: Option<AbsPathBuf>,
+ scope: FlycheckScope,
/// CargoHandle exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we
@@ -343,6 +364,7 @@
config,
sysroot_root,
root: Arc::new(workspace_root),
+ scope: FlycheckScope::Workspace,
manifest_path,
command_handle: None,
command_receiver: None,
@@ -376,7 +398,7 @@
}
Event::RequestStateChange(StateChange::Restart {
generation,
- package,
+ scope,
saved_file,
target,
}) => {
@@ -389,11 +411,11 @@
}
}
+ let command = self.check_command(&scope, saved_file.as_deref(), target);
+ self.scope = scope;
self.generation = generation;
- let Some(command) =
- self.check_command(package.as_deref(), saved_file.as_deref(), target)
- else {
+ let Some(command) = command else {
continue;
};
@@ -435,19 +457,55 @@
tracing::trace!(flycheck_id = self.id, "clearing diagnostics");
// We finished without receiving any diagnostics.
// Clear everything for good measure
- self.send(FlycheckMessage::ClearDiagnostics {
- id: self.id,
- kind: ClearDiagnosticsKind::All,
- });
+ match &self.scope {
+ FlycheckScope::Workspace => {
+ self.send(FlycheckMessage::ClearDiagnostics {
+ id: self.id,
+ kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
+ });
+ }
+ FlycheckScope::Package { package, workspace_deps } => {
+ for pkg in
+ std::iter::once(package).chain(workspace_deps.iter().flatten())
+ {
+ self.send(FlycheckMessage::ClearDiagnostics {
+ id: self.id,
+ kind: ClearDiagnosticsKind::All(ClearScope::Package(
+ pkg.clone(),
+ )),
+ });
+ }
+ }
+ }
} else if res.is_ok() {
// We clear diagnostics for packages on
// `[CargoCheckMessage::CompilerArtifact]` but there seem to be setups where
// cargo may not report an artifact to our runner at all. To handle such
// cases, clear stale diagnostics when flycheck completes successfully.
- self.send(FlycheckMessage::ClearDiagnostics {
- id: self.id,
- kind: ClearDiagnosticsKind::OlderThan(self.generation),
- });
+ match &self.scope {
+ FlycheckScope::Workspace => {
+ self.send(FlycheckMessage::ClearDiagnostics {
+ id: self.id,
+ kind: ClearDiagnosticsKind::OlderThan(
+ self.generation,
+ ClearScope::Workspace,
+ ),
+ });
+ }
+ FlycheckScope::Package { package, workspace_deps } => {
+ for pkg in
+ std::iter::once(package).chain(workspace_deps.iter().flatten())
+ {
+ self.send(FlycheckMessage::ClearDiagnostics {
+ id: self.id,
+ kind: ClearDiagnosticsKind::OlderThan(
+ self.generation,
+ ClearScope::Package(pkg.clone()),
+ ),
+ });
+ }
+ }
+ }
}
self.clear_diagnostics_state();
@@ -475,7 +533,7 @@
);
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
- kind: ClearDiagnosticsKind::Package(package_id),
+ kind: ClearDiagnosticsKind::All(ClearScope::Package(package_id)),
});
}
}
@@ -498,7 +556,9 @@
);
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
- kind: ClearDiagnosticsKind::Package(package_id.clone()),
+ kind: ClearDiagnosticsKind::All(ClearScope::Package(
+ package_id.clone(),
+ )),
});
}
} else if self.diagnostics_received
@@ -507,7 +567,7 @@
self.diagnostics_received = DiagnosticsReceived::YesAndClearedForAll;
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
- kind: ClearDiagnosticsKind::All,
+ kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
});
}
self.send(FlycheckMessage::AddDiagnostic {
@@ -548,7 +608,7 @@
/// return None.
fn check_command(
&self,
- package: Option<&str>,
+ scope: &FlycheckScope,
saved_file: Option<&AbsPath>,
target: Option<Target>,
) -> Option<Command> {
@@ -564,9 +624,9 @@
}
cmd.arg(command);
- match package {
- Some(pkg) => cmd.arg("-p").arg(pkg),
- None => cmd.arg("--workspace"),
+ match scope {
+ FlycheckScope::Workspace => cmd.arg("--workspace"),
+ FlycheckScope::Package { package, .. } => cmd.arg("-p").arg(&package.repr),
};
if let Some(tgt) = target {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 89d6fb8..ce6644f 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -9,6 +9,7 @@
time::{Duration, Instant},
};
+use cargo_metadata::PackageId;
use crossbeam_channel::{Receiver, Sender, unbounded};
use hir::ChangeWithProcMacros;
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
@@ -784,6 +785,7 @@
cargo_toml: package_data.manifest.clone(),
crate_id,
package: cargo.package_flag(package_data),
+ package_id: package_data.id.clone(),
target: target_data.name.clone(),
target_kind: target_data.kind,
required_features: target_data.required_features.clone(),
@@ -812,6 +814,27 @@
None
}
+ pub(crate) fn all_workspace_dependencies_for_package(
+ &self,
+ package: &Arc<PackageId>,
+ ) -> Option<FxHashSet<Arc<PackageId>>> {
+ for workspace in self.workspaces.iter() {
+ match &workspace.kind {
+ ProjectWorkspaceKind::Cargo { cargo, .. }
+ | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
+ let package = cargo.packages().find(|p| cargo[*p].id == *package)?;
+
+ return cargo[package]
+ .all_member_deps
+ .as_ref()
+ .map(|deps| deps.iter().map(|dep| cargo[*dep].id.clone()).collect());
+ }
+ _ => {}
+ }
+ }
+ None
+ }
+
pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
self.vfs.read().0.exists(file_id)
}
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 68c91a6..87be09d 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -331,7 +331,7 @@
let target = TargetSpec::for_file(&world, file_id)?.and_then(|it| {
let tgt_kind = it.target_kind();
let (tgt_name, root, package) = match it {
- TargetSpec::Cargo(c) => (c.target, c.workspace_root, c.package),
+ TargetSpec::Cargo(c) => (c.target, c.workspace_root, c.package_id),
_ => return None,
};
@@ -368,7 +368,13 @@
_ => false,
});
if let Some(idx) = package_workspace_idx {
- world.flycheck[idx].restart_for_package(package, target);
+ let workspace_deps =
+ world.all_workspace_dependencies_for_package(&package);
+ world.flycheck[idx].restart_for_package(
+ package,
+ target,
+ workspace_deps,
+ );
}
}
}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c6762f3..3e80e8b 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -20,7 +20,7 @@
config::Config,
diagnostics::{DiagnosticsGeneration, NativeDiagnosticsFetchKind, fetch_native_diagnostics},
discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage},
- flycheck::{self, ClearDiagnosticsKind, FlycheckMessage},
+ flycheck::{self, ClearDiagnosticsKind, ClearScope, FlycheckMessage},
global_state::{
FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState,
file_id_to_url, url_to_file_id,
@@ -1042,17 +1042,22 @@
};
}
}
- FlycheckMessage::ClearDiagnostics { id, kind: ClearDiagnosticsKind::All } => {
- self.diagnostics.clear_check(id)
- }
FlycheckMessage::ClearDiagnostics {
id,
- kind: ClearDiagnosticsKind::OlderThan(generation),
+ kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
+ } => self.diagnostics.clear_check(id),
+ FlycheckMessage::ClearDiagnostics {
+ id,
+ kind: ClearDiagnosticsKind::All(ClearScope::Package(package_id)),
+ } => self.diagnostics.clear_check_for_package(id, package_id),
+ FlycheckMessage::ClearDiagnostics {
+ id,
+ kind: ClearDiagnosticsKind::OlderThan(generation, ClearScope::Workspace),
} => self.diagnostics.clear_check_older_than(id, generation),
FlycheckMessage::ClearDiagnostics {
id,
- kind: ClearDiagnosticsKind::Package(package_id),
- } => self.diagnostics.clear_check_for_package(id, package_id),
+ kind: ClearDiagnosticsKind::OlderThan(generation, ClearScope::Package(package_id)),
+ } => self.diagnostics.clear_check_older_than_for_package(id, package_id, generation),
FlycheckMessage::Progress { id, progress } => {
let (state, message) = match progress {
flycheck::Progress::DidStart => (Progress::Begin, None),
diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs
index 7132e09..e532d15 100644
--- a/crates/rust-analyzer/src/target_spec.rs
+++ b/crates/rust-analyzer/src/target_spec.rs
@@ -2,12 +2,14 @@
use std::mem;
+use cargo_metadata::PackageId;
use cfg::{CfgAtom, CfgExpr};
use hir::sym;
use ide::{Cancellable, Crate, FileId, RunnableKind, TestId};
use project_model::project_json::Runnable;
use project_model::{CargoFeatures, ManifestPath, TargetKind};
use rustc_hash::FxHashSet;
+use triomphe::Arc;
use vfs::AbsPathBuf;
use crate::global_state::GlobalStateSnapshot;
@@ -52,6 +54,7 @@
pub(crate) workspace_root: AbsPathBuf,
pub(crate) cargo_toml: ManifestPath,
pub(crate) package: String,
+ pub(crate) package_id: Arc<PackageId>,
pub(crate) target: String,
pub(crate) target_kind: TargetKind,
pub(crate) crate_id: Crate,
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 978c50d..5fa0074 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -187,11 +187,19 @@
}
pub fn replace(buf: &mut String, from: char, to: &str) {
- if !buf.contains(from) {
+ let replace_count = buf.chars().filter(|&ch| ch == from).count();
+ if replace_count == 0 {
return;
}
- // FIXME: do this in place.
- *buf = buf.replace(from, to);
+ let from_len = from.len_utf8();
+ let additional = to.len().saturating_sub(from_len);
+ buf.reserve(additional * replace_count);
+
+ let mut end = buf.len();
+ while let Some(i) = buf[..end].rfind(from) {
+ buf.replace_range(i..i + from_len, to);
+ end = i;
+ }
}
#[must_use]
@@ -343,4 +351,34 @@
"fn main() {\n return 92;\n}\n"
);
}
+
+ #[test]
+ fn test_replace() {
+ #[track_caller]
+ fn test_replace(src: &str, from: char, to: &str, expected: &str) {
+ let mut s = src.to_owned();
+ replace(&mut s, from, to);
+ assert_eq!(s, expected, "from: {from:?}, to: {to:?}");
+ }
+
+ test_replace("", 'a', "b", "");
+ test_replace("", 'a', "😀", "");
+ test_replace("", '😀', "a", "");
+ test_replace("a", 'a', "b", "b");
+ test_replace("aa", 'a', "b", "bb");
+ test_replace("ada", 'a', "b", "bdb");
+ test_replace("a", 'a', "😀", "😀");
+ test_replace("😀", '😀', "a", "a");
+ test_replace("😀x", '😀', "a", "ax");
+ test_replace("y😀x", '😀', "a", "yax");
+ test_replace("a,b,c", ',', ".", "a.b.c");
+ test_replace("a,b,c", ',', "..", "a..b..c");
+ test_replace("a.b.c", '.', "..", "a..b..c");
+ test_replace("a.b.c", '.', "..", "a..b..c");
+ test_replace("a😀b😀c", '😀', ".", "a.b.c");
+ test_replace("a.b.c", '.', "😀", "a😀b😀c");
+ test_replace("a.b.c", '.', "😀😀", "a😀😀b😀😀c");
+ test_replace(".a.b.c.", '.', "()", "()a()b()c()");
+ test_replace(".a.b.c.", '.', "", "abc");
+ }
}
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index 50dacd8..e78f1b4 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -1046,6 +1046,8 @@
Maximum length for inlay hints. Set to null to have an unlimited length.
+**Note:** This is mostly a hint, and we don't guarantee to strictly follow the limit.
+
## rust-analyzer.inlayHints.parameterHints.enable {#inlayHints.parameterHints.enable}
diff --git a/editors/code/package.json b/editors/code/package.json
index 1d27a12..745e0da 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -2355,7 +2355,7 @@
"title": "Inlay Hints",
"properties": {
"rust-analyzer.inlayHints.maxLength": {
- "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.",
+ "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.\n\n**Note:** This is mostly a hint, and we don't guarantee to strictly follow the limit.",
"default": 25,
"type": [
"null",