Auto merge of #18099 - ChayimFriedman2:diag-only-necessary, r=Veykril
Use more correct handling of lint attributes
The previous analysis was top-down, and worked on a single file (expanding macros). The new analysis is bottom-up, starting from the diagnostics and climbing up the syntax and module tree.
While this is more efficient (and in fact, efficiency was the motivating reason to work on this), unfortunately the code was already fast enough. But luckily, it also fixes a correctness problem: outline parent modules' attributes were not respected for the previous analysis. Case lints specifically did their own analysis to accommodate that, but it was limited to only them. The new analysis works on all kinds of lints, present and future.
It was basically impossible to fix the old analysis without rewriting it because navigating the module hierarchy must come bottom-up, and if we already have a bottom-up analysis (including syntax analysis because modules can be nested in other syntax elements, including macros), it makes sense to use only this kind of analysis.
Few other bugs (not fundamental to the previous analysis) are also fixed, e.g. overwriting of lint levels (i.e. `#[allow(lint)] mod foo { #[warn(lint)] mod bar; }`.
After this PR is merged I intend to work on an editor command that does workspace-wide diagnostics analysis (that is, `rust-analyzer diagnostics` but from your editor and without having to spawn a new process, which will have to analyze the workspace from scratch). This can be useful to users who do not want to enable check on save because of its overhead, but want to see workspace wide diagnostics from r-a (or to maintainers of rust-analyzer).
Closes #18086.
Closes #18081.
Fixes #18056.
diff --git a/crates/hir-def/src/body/lower/asm.rs b/crates/hir-def/src/body/lower/asm.rs
index 448bc2f..4213370 100644
--- a/crates/hir-def/src/body/lower/asm.rs
+++ b/crates/hir-def/src/body/lower/asm.rs
@@ -87,45 +87,64 @@
);
AsmOperand::In { reg, expr }
} else if dir_spec.out_token().is_some() {
- let expr = self.collect_expr_opt(
- op.asm_operand_expr().and_then(|it| it.in_expr()),
- );
- AsmOperand::Out { reg, expr: Some(expr), late: false }
+ let expr = op
+ .asm_operand_expr()
+ .and_then(|it| it.in_expr())
+ .filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
+ .map(|expr| self.collect_expr(expr));
+ AsmOperand::Out { reg, expr, late: false }
} else if dir_spec.lateout_token().is_some() {
- let expr = self.collect_expr_opt(
- op.asm_operand_expr().and_then(|it| it.in_expr()),
- );
- AsmOperand::Out { reg, expr: Some(expr), late: true }
+ let expr = op
+ .asm_operand_expr()
+ .and_then(|it| it.in_expr())
+ .filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
+ .map(|expr| self.collect_expr(expr));
+
+ AsmOperand::Out { reg, expr, late: true }
} else if dir_spec.inout_token().is_some() {
let Some(op_expr) = op.asm_operand_expr() else { continue };
let in_expr = self.collect_expr_opt(op_expr.in_expr());
- let out_expr =
- op_expr.out_expr().map(|it| self.collect_expr(it));
- match out_expr {
- Some(out_expr) => AsmOperand::SplitInOut {
- reg,
- in_expr,
- out_expr: Some(out_expr),
- late: false,
- },
- None => {
+ match op_expr.fat_arrow_token().is_some() {
+ true => {
+ let out_expr = op_expr
+ .out_expr()
+ .filter(|it| {
+ !matches!(it, ast::Expr::UnderscoreExpr(_))
+ })
+ .map(|expr| self.collect_expr(expr));
+
+ AsmOperand::SplitInOut {
+ reg,
+ in_expr,
+ out_expr,
+ late: false,
+ }
+ }
+ false => {
AsmOperand::InOut { reg, expr: in_expr, late: false }
}
}
} else if dir_spec.inlateout_token().is_some() {
let Some(op_expr) = op.asm_operand_expr() else { continue };
let in_expr = self.collect_expr_opt(op_expr.in_expr());
- let out_expr =
- op_expr.out_expr().map(|it| self.collect_expr(it));
- match out_expr {
- Some(out_expr) => AsmOperand::SplitInOut {
- reg,
- in_expr,
- out_expr: Some(out_expr),
- late: false,
- },
- None => {
- AsmOperand::InOut { reg, expr: in_expr, late: false }
+ match op_expr.fat_arrow_token().is_some() {
+ true => {
+ let out_expr = op_expr
+ .out_expr()
+ .filter(|it| {
+ !matches!(it, ast::Expr::UnderscoreExpr(_))
+ })
+ .map(|expr| self.collect_expr(expr));
+
+ AsmOperand::SplitInOut {
+ reg,
+ in_expr,
+ out_expr,
+ late: true,
+ }
+ }
+ false => {
+ AsmOperand::InOut { reg, expr: in_expr, late: true }
}
}
} else {
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index b79aa89..a04e7b1 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -895,21 +895,52 @@
TyKind::Scalar(Scalar::Int(primitive::int_ty_from_builtin(*int_ty)))
.intern(Interner)
}
- None => self.table.new_integer_var(),
+ None => {
+ let expected_ty = expected.to_option(&mut self.table);
+ let opt_ty = match expected_ty.as_ref().map(|it| it.kind(Interner)) {
+ Some(TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))) => expected_ty,
+ Some(TyKind::Scalar(Scalar::Char)) => {
+ Some(TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner))
+ }
+ Some(TyKind::Raw(..) | TyKind::FnDef(..) | TyKind::Function(..)) => {
+ Some(TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner))
+ }
+ _ => None,
+ };
+ opt_ty.unwrap_or_else(|| self.table.new_integer_var())
+ }
},
Literal::Uint(_v, ty) => match ty {
Some(int_ty) => {
TyKind::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(*int_ty)))
.intern(Interner)
}
- None => self.table.new_integer_var(),
+ None => {
+ let expected_ty = expected.to_option(&mut self.table);
+ let opt_ty = match expected_ty.as_ref().map(|it| it.kind(Interner)) {
+ Some(TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))) => expected_ty,
+ Some(TyKind::Scalar(Scalar::Char)) => {
+ Some(TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner))
+ }
+ Some(TyKind::Raw(..) | TyKind::FnDef(..) | TyKind::Function(..)) => {
+ Some(TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner))
+ }
+ _ => None,
+ };
+ opt_ty.unwrap_or_else(|| self.table.new_integer_var())
+ }
},
Literal::Float(_v, ty) => match ty {
Some(float_ty) => {
TyKind::Scalar(Scalar::Float(primitive::float_ty_from_builtin(*float_ty)))
.intern(Interner)
}
- None => self.table.new_float_var(),
+ None => {
+ let opt_ty = expected.to_option(&mut self.table).filter(|ty| {
+ matches!(ty.kind(Interner), TyKind::Scalar(Scalar::Float(_)))
+ });
+ opt_ty.unwrap_or_else(|| self.table.new_float_var())
+ }
},
},
Expr::Underscore => {
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 908bbc2..2735719 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -49,7 +49,7 @@
//- minicore: coerce_unsized
fn test() {
let x: &[isize] = &[1];
- // ^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not)), Pointer(Unsize)
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?2, Not)), Pointer(Unsize)
let x: *const [isize] = &[1];
// ^^^^ adjustments: Deref(None), Borrow(RawPtr(Not)), Pointer(Unsize)
}
@@ -148,7 +148,7 @@
fn test(i: i32) {
let x = match i {
2 => foo(&[2]),
- // ^^^^ adjustments: Deref(None), Borrow(Ref('?10, Not)), Pointer(Unsize)
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?8, Not)), Pointer(Unsize)
1 => &[1],
_ => &[3],
};
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index 37280f8..0473ee0 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -917,7 +917,7 @@
278..279 'A': extern "rust-call" A<i32>(*mut i32) -> A<i32>
278..292 'A(0 as *mut _)': A<i32>
278..307 'A(0 as...B(a)))': &'? i32
- 280..281 '0': i32
+ 280..281 '0': usize
280..291 '0 as *mut _': *mut i32
297..306 '&&B(B(a))': &'? &'? B<B<A<i32>>>
298..306 '&B(B(a))': &'? B<B<A<i32>>>
diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
index b6abb06..a211ca8 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -1,12 +1,13 @@
use std::iter::{self, Peekable};
use either::Either;
-use hir::{sym, Adt, Crate, HasAttrs, HasSource, ImportPathConfig, ModuleDef, Semantics};
+use hir::{sym, Adt, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics};
+use ide_db::syntax_helpers::suggest_name;
use ide_db::RootDatabase;
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
use itertools::Itertools;
use syntax::ast::edit_in_place::Removable;
-use syntax::ast::{self, make, AstNode, HasName, MatchArmList, MatchExpr, Pat};
+use syntax::ast::{self, make, AstNode, MatchArmList, MatchExpr, Pat};
use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
@@ -90,7 +91,7 @@
.into_iter()
.filter_map(|variant| {
Some((
- build_pat(ctx.db(), module, variant, cfg)?,
+ build_pat(ctx, module, variant, cfg)?,
variant.should_be_hidden(ctx.db(), module.krate()),
))
})
@@ -141,9 +142,8 @@
let is_hidden = variants
.iter()
.any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
- let patterns = variants
- .into_iter()
- .filter_map(|variant| build_pat(ctx.db(), module, variant, cfg));
+ let patterns =
+ variants.into_iter().filter_map(|variant| build_pat(ctx, module, variant, cfg));
(ast::Pat::from(make::tuple_pat(patterns)), is_hidden)
})
@@ -174,9 +174,8 @@
let is_hidden = variants
.iter()
.any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
- let patterns = variants
- .into_iter()
- .filter_map(|variant| build_pat(ctx.db(), module, variant, cfg));
+ let patterns =
+ variants.into_iter().filter_map(|variant| build_pat(ctx, module, variant, cfg));
(ast::Pat::from(make::slice_pat(patterns)), is_hidden)
})
.filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
@@ -438,33 +437,39 @@
}
fn build_pat(
- db: &RootDatabase,
+ ctx: &AssistContext<'_>,
module: hir::Module,
var: ExtendedVariant,
cfg: ImportPathConfig,
) -> Option<ast::Pat> {
+ let db = ctx.db();
match var {
ExtendedVariant::Variant(var) => {
let edition = module.krate().edition(db);
let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition);
- // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
- Some(match var.source(db)?.value.kind() {
- ast::StructKind::Tuple(field_list) => {
- let pats =
- iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
+ let fields = var.fields(db);
+ let pat = match var.kind(db) {
+ hir::StructKind::Tuple => {
+ let mut name_generator = suggest_name::NameGenerator::new();
+ let pats = fields.into_iter().map(|f| {
+ let name = name_generator.for_type(&f.ty(db), db, edition);
+ match name {
+ Some(name) => make::ext::simple_ident_pat(make::name(&name)).into(),
+ None => make::wildcard_pat().into(),
+ }
+ });
make::tuple_struct_pat(path, pats).into()
}
- ast::StructKind::Record(field_list) => {
- let pats = field_list.fields().map(|f| {
- make::ext::simple_ident_pat(
- f.name().expect("Record field must have a name"),
- )
- .into()
- });
+ hir::StructKind::Record => {
+ let pats = fields
+ .into_iter()
+ .map(|f| make::name(f.name(db).as_str()))
+ .map(|name| make::ext::simple_ident_pat(name).into());
make::record_pat(path, pats).into()
}
- ast::StructKind::Unit => make::path_pat(path),
- })
+ hir::StructKind::Unit => make::path_pat(path),
+ };
+ Some(pat)
}
ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
@@ -1976,4 +1981,81 @@
}"#,
)
}
+
+ #[test]
+ fn suggest_name_for_tuple_struct_patterns() {
+ // single tuple struct
+ check_assist(
+ add_missing_match_arms,
+ r#"
+struct S;
+
+pub enum E {
+ A
+ B(S),
+}
+
+fn f() {
+ let value = E::A;
+ match value {
+ $0
+ }
+}
+"#,
+ r#"
+struct S;
+
+pub enum E {
+ A
+ B(S),
+}
+
+fn f() {
+ let value = E::A;
+ match value {
+ $0E::A => todo!(),
+ E::B(s) => todo!(),
+ }
+}
+"#,
+ );
+
+ // multiple tuple struct patterns
+ check_assist(
+ add_missing_match_arms,
+ r#"
+struct S1;
+struct S2;
+
+pub enum E {
+ A
+ B(S1, S2),
+}
+
+fn f() {
+ let value = E::A;
+ match value {
+ $0
+ }
+}
+"#,
+ r#"
+struct S1;
+struct S2;
+
+pub enum E {
+ A
+ B(S1, S2),
+}
+
+fn f() {
+ let value = E::A;
+ match value {
+ $0E::A => todo!(),
+ E::B(s1, s2) => todo!(),
+ }
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs
index c22d195..a55323e 100644
--- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs
+++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs
@@ -584,7 +584,7 @@
for old_strukt_param in old_strukt_params.generic_params() {
// Get old name from `strukt`
- let mut name = SmolStr::from(match &old_strukt_param {
+ let name = SmolStr::from(match &old_strukt_param {
ast::GenericParam::ConstParam(c) => c.name()?.to_string(),
ast::GenericParam::LifetimeParam(l) => {
l.lifetime()?.lifetime_ident_token()?.to_string()
@@ -593,8 +593,19 @@
});
// The new name cannot be conflicted with generics in trait, and the renamed names.
- name = suggest_name::for_unique_generic_name(&name, old_impl_params);
- name = suggest_name::for_unique_generic_name(&name, ¶ms);
+ let param_list_to_names = |param_list: &GenericParamList| {
+ param_list.generic_params().flat_map(|param| match param {
+ ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
+ p => Some(p.to_string()),
+ })
+ };
+ let existing_names = param_list_to_names(old_impl_params)
+ .chain(param_list_to_names(¶ms))
+ .collect_vec();
+ let mut name_generator = suggest_name::NameGenerator::new_with_names(
+ existing_names.iter().map(|s| s.as_str()),
+ );
+ let name = name_generator.suggest_name(&name);
match old_strukt_param {
ast::GenericParam::ConstParam(c) => {
if let Some(const_ty) = c.ty() {
@@ -1213,9 +1224,9 @@
b : B<T>,
}
-impl<T0> Trait<T0> for S<T0> {
- fn f(&self, a: T0) -> T0 {
- <B<T0> as Trait<T0>>::f(&self.b, a)
+impl<T1> Trait<T1> for S<T1> {
+ fn f(&self, a: T1) -> T1 {
+ <B<T1> as Trait<T1>>::f(&self.b, a)
}
}
"#,
@@ -1527,12 +1538,12 @@
b : B<T, T1>,
}
-impl<T, T2, T10> Trait<T> for S<T2, T10>
+impl<T, T2, T3> Trait<T> for S<T2, T3>
where
- T10: AnotherTrait
+ T3: AnotherTrait
{
fn f(&self, a: T) -> T {
- <B<T2, T10> as Trait<T>>::f(&self.b, a)
+ <B<T2, T3> as Trait<T>>::f(&self.b, a)
}
}"#,
);
@@ -1589,12 +1600,12 @@
b : B<T>,
}
-impl<T, T0> Trait<T> for S<T0>
+impl<T, T2> Trait<T> for S<T2>
where
- T0: AnotherTrait
+ T2: AnotherTrait
{
fn f(&self, a: T) -> T {
- <B<T0> as Trait<T>>::f(&self.b, a)
+ <B<T2> as Trait<T>>::f(&self.b, a)
}
}"#,
);
diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs
index a734a6c..bf6ac17 100644
--- a/crates/ide-assists/src/handlers/introduce_named_generic.rs
+++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs
@@ -1,6 +1,7 @@
use ide_db::syntax_helpers::suggest_name;
+use itertools::Itertools;
use syntax::{
- ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams},
+ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName},
ted,
};
@@ -33,8 +34,18 @@
let impl_trait_type = edit.make_mut(impl_trait_type);
let fn_ = edit.make_mut(fn_);
let fn_generic_param_list = fn_.get_or_create_generic_param_list();
- let type_param_name =
- suggest_name::for_impl_trait_as_generic(&impl_trait_type, &fn_generic_param_list);
+
+ let existing_names = fn_generic_param_list
+ .generic_params()
+ .flat_map(|param| match param {
+ ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
+ p => Some(p.to_string()),
+ })
+ .collect_vec();
+ let type_param_name = suggest_name::NameGenerator::new_with_names(
+ existing_names.iter().map(|s| s.as_str()),
+ )
+ .for_impl_trait_as_generic(&impl_trait_type);
let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
.clone_for_update();
@@ -116,7 +127,7 @@
check_assist(
introduce_named_generic,
r#"fn foo<B>(bar: $0impl Bar) {}"#,
- r#"fn foo<B, $0B0: Bar>(bar: B0) {}"#,
+ r#"fn foo<B, $0B1: Bar>(bar: B1) {}"#,
);
}
@@ -125,7 +136,7 @@
check_assist(
introduce_named_generic,
r#"fn foo<B, B0, B1, B3>(bar: $0impl Bar) {}"#,
- r#"fn foo<B, B0, B1, B3, $0B2: Bar>(bar: B2) {}"#,
+ r#"fn foo<B, B0, B1, B3, $0B4: Bar>(bar: B4) {}"#,
);
}
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index 2a06fc4..8f38e02 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -48,11 +48,12 @@
// Suggest name only in let-stmt and fn param
if pattern_ctx.should_suggest_name {
+ let mut name_generator = suggest_name::NameGenerator::new();
if let Some(suggested) = ctx
.expected_type
.as_ref()
.map(|ty| ty.strip_references())
- .and_then(|ty| suggest_name::for_type(&ty, ctx.db, ctx.edition))
+ .and_then(|ty| name_generator.for_type(&ty, ctx.db, ctx.edition))
{
acc.suggest_name(ctx, &suggested);
}
diff --git a/crates/ide-db/src/syntax_helpers/suggest_name.rs b/crates/ide-db/src/syntax_helpers/suggest_name.rs
index e60deb3..2679cbe 100644
--- a/crates/ide-db/src/syntax_helpers/suggest_name.rs
+++ b/crates/ide-db/src/syntax_helpers/suggest_name.rs
@@ -1,12 +1,14 @@
//! This module contains functions to suggest names for expressions, functions and other items
+use std::{collections::hash_map::Entry, str::FromStr};
+
use hir::Semantics;
use itertools::Itertools;
-use rustc_hash::FxHashSet;
+use rustc_hash::FxHashMap;
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, HasName},
- match_ast, AstNode, Edition, SmolStr,
+ match_ast, AstNode, Edition, SmolStr, SmolStrBuilder,
};
use crate::RootDatabase;
@@ -62,71 +64,131 @@
"into_future",
];
-/// Suggest a name for given type.
+/// Generator for new names
///
-/// The function will strip references first, and suggest name from the inner type.
+/// The generator keeps track of existing names and suggests new names that do
+/// not conflict with existing names.
///
-/// - If `ty` is an ADT, it will suggest the name of the ADT.
-/// + If `ty` is wrapped in `Box`, `Option` or `Result`, it will suggest the name from the inner type.
-/// - If `ty` is a trait, it will suggest the name of the trait.
-/// - If `ty` is an `impl Trait`, it will suggest the name of the first trait.
+/// The generator will try to resolve conflicts by adding a numeric suffix to
+/// the name, e.g. `a`, `a1`, `a2`, ...
///
-/// If the suggested name conflicts with reserved keywords, it will return `None`.
-pub fn for_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option<String> {
- let ty = ty.strip_references();
- name_of_type(&ty, db, edition)
+/// # Examples
+/// ```rust
+/// let mut generator = NameGenerator::new();
+/// assert_eq!(generator.suggest_name("a"), "a");
+/// assert_eq!(generator.suggest_name("a"), "a1");
+///
+/// assert_eq!(generator.suggest_name("b2"), "b2");
+/// assert_eq!(generator.suggest_name("b"), "b3");
+/// ```
+#[derive(Debug, Default)]
+pub struct NameGenerator {
+ pool: FxHashMap<SmolStr, usize>,
}
-/// Suggest a unique name for generic parameter.
-///
-/// `existing_params` is used to check if the name conflicts with existing
-/// generic parameters.
-///
-/// The function checks if the name conflicts with existing generic parameters.
-/// If so, it will try to resolve the conflict by adding a number suffix, e.g.
-/// `T`, `T0`, `T1`, ...
-pub fn for_unique_generic_name(name: &str, existing_params: &ast::GenericParamList) -> SmolStr {
- let param_names = existing_params
- .generic_params()
- .map(|param| match param {
- ast::GenericParam::TypeParam(t) => t.name().unwrap().to_string(),
- p => p.to_string(),
- })
- .collect::<FxHashSet<_>>();
- let mut name = name.to_owned();
- let base_len = name.len();
- let mut count = 0;
- while param_names.contains(&name) {
- name.truncate(base_len);
- name.push_str(&count.to_string());
- count += 1;
+impl NameGenerator {
+ /// Create a new empty generator
+ pub fn new() -> Self {
+ Self { pool: FxHashMap::default() }
}
- name.into()
-}
+ /// Create a new generator with existing names. When suggesting a name, it will
+ /// avoid conflicts with existing names.
+ pub fn new_with_names<'a>(existing_names: impl Iterator<Item = &'a str>) -> Self {
+ let mut generator = Self::new();
+ existing_names.for_each(|name| generator.insert(name));
+ generator
+ }
-/// Suggest name of impl trait type
-///
-/// `existing_params` is used to check if the name conflicts with existing
-/// generic parameters.
-///
-/// # Current implementation
-///
-/// In current implementation, the function tries to get the name from the first
-/// character of the name for the first type bound.
-///
-/// If the name conflicts with existing generic parameters, it will try to
-/// resolve the conflict with `for_unique_generic_name`.
-pub fn for_impl_trait_as_generic(
- ty: &ast::ImplTraitType,
- existing_params: &ast::GenericParamList,
-) -> SmolStr {
- let c = ty
- .type_bound_list()
- .and_then(|bounds| bounds.syntax().text().char_at(0.into()))
- .unwrap_or('T');
+ /// Suggest a name without conflicts. If the name conflicts with existing names,
+ /// it will try to resolve the conflict by adding a numeric suffix.
+ pub fn suggest_name(&mut self, name: &str) -> SmolStr {
+ let (prefix, suffix) = Self::split_numeric_suffix(name);
+ let prefix = SmolStr::new(prefix);
+ let suffix = suffix.unwrap_or(0);
- for_unique_generic_name(c.encode_utf8(&mut [0; 4]), existing_params)
+ match self.pool.entry(prefix.clone()) {
+ Entry::Vacant(entry) => {
+ entry.insert(suffix);
+ SmolStr::from_str(name).unwrap()
+ }
+ Entry::Occupied(mut entry) => {
+ let count = entry.get_mut();
+ *count = (*count + 1).max(suffix);
+
+ let mut new_name = SmolStrBuilder::new();
+ new_name.push_str(&prefix);
+ new_name.push_str(count.to_string().as_str());
+ new_name.finish()
+ }
+ }
+ }
+
+ /// Suggest a name for given type.
+ ///
+ /// The function will strip references first, and suggest name from the inner type.
+ ///
+ /// - If `ty` is an ADT, it will suggest the name of the ADT.
+ /// + If `ty` is wrapped in `Box`, `Option` or `Result`, it will suggest the name from the inner type.
+ /// - If `ty` is a trait, it will suggest the name of the trait.
+ /// - If `ty` is an `impl Trait`, it will suggest the name of the first trait.
+ ///
+ /// If the suggested name conflicts with reserved keywords, it will return `None`.
+ pub fn for_type(
+ &mut self,
+ ty: &hir::Type,
+ db: &RootDatabase,
+ edition: Edition,
+ ) -> Option<SmolStr> {
+ let name = name_of_type(ty, db, edition)?;
+ Some(self.suggest_name(&name))
+ }
+
+ /// Suggest name of impl trait type
+ ///
+ /// # Current implementation
+ ///
+ /// In current implementation, the function tries to get the name from the first
+ /// character of the name for the first type bound.
+ ///
+ /// If the name conflicts with existing generic parameters, it will try to
+ /// resolve the conflict with `for_unique_generic_name`.
+ pub fn for_impl_trait_as_generic(&mut self, ty: &ast::ImplTraitType) -> SmolStr {
+ let c = ty
+ .type_bound_list()
+ .and_then(|bounds| bounds.syntax().text().char_at(0.into()))
+ .unwrap_or('T');
+
+ self.suggest_name(&c.to_string())
+ }
+
+ /// Insert a name into the pool
+ fn insert(&mut self, name: &str) {
+ let (prefix, suffix) = Self::split_numeric_suffix(name);
+ let prefix = SmolStr::new(prefix);
+ let suffix = suffix.unwrap_or(0);
+
+ match self.pool.entry(prefix) {
+ Entry::Vacant(entry) => {
+ entry.insert(suffix);
+ }
+ Entry::Occupied(mut entry) => {
+ let count = entry.get_mut();
+ *count = (*count).max(suffix);
+ }
+ }
+ }
+
+ /// Remove the numeric suffix from the name
+ ///
+ /// # Examples
+ /// `a1b2c3` -> `a1b2c`
+ fn split_numeric_suffix(name: &str) -> (&str, Option<usize>) {
+ let pos =
+ name.rfind(|c: char| !c.is_numeric()).expect("Name cannot be empty or all-numeric");
+ let (prefix, suffix) = name.split_at(pos + 1);
+ (prefix, suffix.parse().ok())
+ }
}
/// Suggest name of variable for given expression
@@ -290,9 +352,10 @@
fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
let ty = sema.type_of_expr(expr)?.adjusted();
+ let ty = ty.remove_ref().unwrap_or(ty);
let edition = sema.scope(expr.syntax())?.krate().edition(sema.db);
- for_type(&ty, sema.db, edition)
+ name_of_type(&ty, sema.db, edition)
}
fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option<String> {
@@ -925,4 +988,29 @@
"bar",
);
}
+
+ #[test]
+ fn conflicts_with_existing_names() {
+ let mut generator = NameGenerator::new();
+ assert_eq!(generator.suggest_name("a"), "a");
+ assert_eq!(generator.suggest_name("a"), "a1");
+ assert_eq!(generator.suggest_name("a"), "a2");
+ assert_eq!(generator.suggest_name("a"), "a3");
+
+ assert_eq!(generator.suggest_name("b"), "b");
+ assert_eq!(generator.suggest_name("b2"), "b2");
+ assert_eq!(generator.suggest_name("b"), "b3");
+ assert_eq!(generator.suggest_name("b"), "b4");
+ assert_eq!(generator.suggest_name("b3"), "b5");
+
+ // ---------
+ let mut generator = NameGenerator::new_with_names(["a", "b", "b2", "c4"].into_iter());
+ assert_eq!(generator.suggest_name("a"), "a1");
+ assert_eq!(generator.suggest_name("a"), "a2");
+
+ assert_eq!(generator.suggest_name("b"), "b3");
+ assert_eq!(generator.suggest_name("b2"), "b4");
+
+ assert_eq!(generator.suggest_name("c"), "c5");
+ }
}
diff --git a/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
index 90527c5..ad4baf5 100644
--- a/crates/ide-diagnostics/src/handlers/invalid_cast.rs
+++ b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
@@ -441,16 +441,16 @@
//^^^^^^^^^^^^^^^^^ error: cannot cast thin pointer `*const i32` to fat pointer `*const [i32]`
let t: *mut (dyn Trait + 'static) = 0 as *mut _;
- //^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*mut _`
+ //^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*mut _`
let mut fail: *const str = 0 as *const str;
- //^^^^^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*const str`
+ //^^^^^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*const str`
let mut fail2: *const str = 0isize as *const str;
//^^^^^^^^^^^^^^^^^^^^ error: cannot cast `isize` to a fat pointer `*const str`
}
fn foo<T: ?Sized>() {
let s = 0 as *const T;
- //^^^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*const T`
+ //^^^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*const T`
}
"#,
&["E0308", "unused_variables"],
@@ -1100,4 +1100,15 @@
"#,
);
}
+
+ #[test]
+ fn cast_literal_to_char() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ 0 as char;
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index b5c242e..6994a7e 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -402,4 +402,26 @@
],
);
}
+
+ #[test]
+ fn underscore_in_asm() {
+ check_diagnostics(
+ r#"
+//- minicore: asm
+fn rdtscp() -> u64 {
+ let hi: u64;
+ let lo: u64;
+ unsafe {
+ core::arch::asm!(
+ "rdtscp",
+ out("rdx") hi,
+ out("rax") lo,
+ out("rcx") _,
+ options(nomem, nostack, preserves_flags)
+ );
+ }
+ (hi << 32) | lo
+}"#,
+ );
+ }
}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 2e49af4..97e7123 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -577,11 +577,14 @@
impl InlayHintLabelBuilder<'_> {
fn make_new_part(&mut self) {
- self.result.parts.push(InlayHintLabelPart {
- text: take(&mut self.last_part),
- linked_location: self.location.take(),
- tooltip: None,
- });
+ let text = take(&mut self.last_part);
+ if !text.is_empty() {
+ self.result.parts.push(InlayHintLabelPart {
+ text,
+ linked_location: self.location.take(),
+ tooltip: None,
+ });
+ }
}
fn finish(mut self) -> InlayHintLabel {
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index df34e4a..58d8f97 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -140,7 +140,6 @@
(
147..172,
[
- "",
InlayHintLabelPart {
text: "B",
linked_location: Some(
@@ -153,13 +152,11 @@
),
tooltip: "",
},
- "",
],
),
(
147..154,
[
- "",
InlayHintLabelPart {
text: "A",
linked_location: Some(
@@ -172,7 +169,6 @@
),
tooltip: "",
},
- "",
],
),
]
@@ -223,7 +219,6 @@
(
143..190,
[
- "",
InlayHintLabelPart {
text: "C",
linked_location: Some(
@@ -236,13 +231,11 @@
),
tooltip: "",
},
- "",
],
),
(
143..179,
[
- "",
InlayHintLabelPart {
text: "B",
linked_location: Some(
@@ -255,7 +248,6 @@
),
tooltip: "",
},
- "",
],
),
]
@@ -290,7 +282,6 @@
(
143..190,
[
- "",
InlayHintLabelPart {
text: "C",
linked_location: Some(
@@ -303,13 +294,11 @@
),
tooltip: "",
},
- "",
],
),
(
143..179,
[
- "",
InlayHintLabelPart {
text: "B",
linked_location: Some(
@@ -322,7 +311,6 @@
),
tooltip: "",
},
- "",
],
),
]
@@ -358,7 +346,6 @@
(
246..283,
[
- "",
InlayHintLabelPart {
text: "B",
linked_location: Some(
@@ -390,7 +377,6 @@
(
246..265,
[
- "",
InlayHintLabelPart {
text: "A",
linked_location: Some(
@@ -563,7 +549,6 @@
),
tooltip: "",
},
- "",
],
),
]
@@ -598,7 +583,6 @@
(
124..130,
[
- "",
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
@@ -611,13 +595,11 @@
),
tooltip: "",
},
- "",
],
),
(
145..185,
[
- "",
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
@@ -630,13 +612,11 @@
),
tooltip: "",
},
- "",
],
),
(
145..168,
[
- "",
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
@@ -649,7 +629,6 @@
),
tooltip: "",
},
- "",
],
),
(
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index 8af5bd5..90b8be6 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -78,7 +78,7 @@
}
closing_token = block.r_curly_token()?;
- let lifetime = label.lifetime().map_or_else(String::new, |it| it.to_string());
+ let lifetime = label.lifetime()?.to_string();
(lifetime, Some(label.syntax().text_range()))
} else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
diff --git a/crates/ide/src/inlay_hints/closure_captures.rs b/crates/ide/src/inlay_hints/closure_captures.rs
index adf7cbc..f399bd0 100644
--- a/crates/ide/src/inlay_hints/closure_captures.rs
+++ b/crates/ide/src/inlay_hints/closure_captures.rs
@@ -3,7 +3,7 @@
//! Tests live in [`bind_pat`][super::bind_pat] module.
use ide_db::famous_defs::FamousDefs;
use span::EditionedFileId;
-use stdx::TupleExt;
+use stdx::{never, TupleExt};
use syntax::ast::{self, AstNode};
use text_edit::{TextRange, TextSize};
@@ -63,17 +63,21 @@
// force cache the source file, otherwise sema lookup will potentially panic
_ = sema.parse_or_expand(source.file());
+ let label = format!(
+ "{}{}",
+ match capture.kind() {
+ hir::CaptureKind::SharedRef => "&",
+ hir::CaptureKind::UniqueSharedRef => "&unique ",
+ hir::CaptureKind::MutableRef => "&mut ",
+ hir::CaptureKind::Move => "",
+ },
+ capture.display_place(sema.db)
+ );
+ if never!(label.is_empty()) {
+ continue;
+ }
let label = InlayHintLabel::simple(
- format!(
- "{}{}",
- match capture.kind() {
- hir::CaptureKind::SharedRef => "&",
- hir::CaptureKind::UniqueSharedRef => "&unique ",
- hir::CaptureKind::MutableRef => "&mut ",
- hir::CaptureKind::Move => "",
- },
- capture.display_place(sema.db)
- ),
+ label,
None,
source.name().and_then(|name| {
name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(Into::into)
diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs
index f03de8c..ed7bf27 100644
--- a/crates/rust-analyzer/src/handlers/dispatch.rs
+++ b/crates/rust-analyzer/src/handlers/dispatch.rs
@@ -325,14 +325,14 @@
pub(crate) fn on_sync_mut<N>(
&mut self,
f: fn(&mut GlobalState, N::Params) -> anyhow::Result<()>,
- ) -> anyhow::Result<&mut Self>
+ ) -> &mut Self
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned + Send + Debug,
{
let not = match self.not.take() {
Some(it) => it,
- None => return Ok(self),
+ None => return self,
};
let _guard = tracing::info_span!("notification", method = ?not.method).entered();
@@ -344,7 +344,7 @@
}
Err(ExtractError::MethodMismatch(not)) => {
self.not = Some(not);
- return Ok(self);
+ return self;
}
};
@@ -355,8 +355,10 @@
version(),
N::METHOD
));
- f(self.global_state, params)?;
- Ok(self)
+ if let Err(e) = f(self.global_state, params) {
+ tracing::error!(handler = %N::METHOD, error = %e, "notification handler failed");
+ }
+ self
}
pub(crate) fn finish(&mut self) {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 12df61a..8355923 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -196,7 +196,7 @@
) {
return Ok(());
}
- self.handle_event(event)?;
+ self.handle_event(event);
}
Err(anyhow::anyhow!("A receiver has been dropped, something panicked!"))
@@ -278,7 +278,7 @@
.map(Some)
}
- fn handle_event(&mut self, event: Event) -> anyhow::Result<()> {
+ fn handle_event(&mut self, event: Event) {
let loop_start = Instant::now();
let _p = tracing::info_span!("GlobalState::handle_event", event = %event).entered();
@@ -295,7 +295,7 @@
match event {
Event::Lsp(msg) => match msg {
lsp_server::Message::Request(req) => self.on_new_request(loop_start, req),
- lsp_server::Message::Notification(not) => self.on_notification(not)?,
+ lsp_server::Message::Notification(not) => self.on_notification(not),
lsp_server::Message::Response(resp) => self.complete_request(resp),
},
Event::QueuedTask(task) => {
@@ -487,7 +487,6 @@
"overly long loop turn took {loop_duration:?} (event handling took {event_handling_duration:?}): {event_dbg_msg}"
));
}
- Ok(())
}
fn prime_caches(&mut self, cause: String) {
@@ -1116,37 +1115,32 @@
}
/// Handles an incoming notification.
- fn on_notification(&mut self, not: Notification) -> anyhow::Result<()> {
+ fn on_notification(&mut self, not: Notification) {
let _p =
span!(Level::INFO, "GlobalState::on_notification", not.method = ?not.method).entered();
use crate::handlers::notification as handlers;
use lsp_types::notification as notifs;
NotificationDispatcher { not: Some(not), global_state: self }
- .on_sync_mut::<notifs::Cancel>(handlers::handle_cancel)?
+ .on_sync_mut::<notifs::Cancel>(handlers::handle_cancel)
.on_sync_mut::<notifs::WorkDoneProgressCancel>(
handlers::handle_work_done_progress_cancel,
- )?
- .on_sync_mut::<notifs::DidOpenTextDocument>(handlers::handle_did_open_text_document)?
- .on_sync_mut::<notifs::DidChangeTextDocument>(
- handlers::handle_did_change_text_document,
- )?
- .on_sync_mut::<notifs::DidCloseTextDocument>(handlers::handle_did_close_text_document)?
- .on_sync_mut::<notifs::DidSaveTextDocument>(handlers::handle_did_save_text_document)?
+ )
+ .on_sync_mut::<notifs::DidOpenTextDocument>(handlers::handle_did_open_text_document)
+ .on_sync_mut::<notifs::DidChangeTextDocument>(handlers::handle_did_change_text_document)
+ .on_sync_mut::<notifs::DidCloseTextDocument>(handlers::handle_did_close_text_document)
+ .on_sync_mut::<notifs::DidSaveTextDocument>(handlers::handle_did_save_text_document)
.on_sync_mut::<notifs::DidChangeConfiguration>(
handlers::handle_did_change_configuration,
- )?
+ )
.on_sync_mut::<notifs::DidChangeWorkspaceFolders>(
handlers::handle_did_change_workspace_folders,
- )?
- .on_sync_mut::<notifs::DidChangeWatchedFiles>(
- handlers::handle_did_change_watched_files,
- )?
- .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck)?
- .on_sync_mut::<lsp_ext::ClearFlycheck>(handlers::handle_clear_flycheck)?
- .on_sync_mut::<lsp_ext::RunFlycheck>(handlers::handle_run_flycheck)?
- .on_sync_mut::<lsp_ext::AbortRunTest>(handlers::handle_abort_run_test)?
+ )
+ .on_sync_mut::<notifs::DidChangeWatchedFiles>(handlers::handle_did_change_watched_files)
+ .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck)
+ .on_sync_mut::<lsp_ext::ClearFlycheck>(handlers::handle_clear_flycheck)
+ .on_sync_mut::<lsp_ext::RunFlycheck>(handlers::handle_run_flycheck)
+ .on_sync_mut::<lsp_ext::AbortRunTest>(handlers::handle_abort_run_test)
.finish();
- Ok(())
}
}
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index dbbb290..c1554c4 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -66,7 +66,7 @@
TokenAtOffset, WalkEvent,
};
pub use rustc_lexer::unescape;
-pub use smol_str::{format_smolstr, SmolStr, ToSmolStr};
+pub use smol_str::{format_smolstr, SmolStr, SmolStrBuilder, ToSmolStr};
/// `Parse` is the result of the parsing: a syntax tree and a collection of
/// errors.