Merge commit '3b7c7f97e4' into sync-from-ra

This commit is contained in:
Laurențiu Nicola 2023-11-08 08:15:03 +02:00
parent 6eaf3f8bb2
commit d1d111d09e
177 changed files with 14930 additions and 2099 deletions

View file

@ -257,6 +257,7 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe {
) -> Result<Subtree, ProcMacroExpansionError>;
}
#[derive(Debug)]
pub enum ProcMacroExpansionError {
Panic(String),
/// Things like "proc macro server was killed by OOM".

View file

@ -75,7 +75,7 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug {
#[salsa::input]
fn crate_graph(&self) -> Arc<CrateGraph>;
/// The crate graph.
/// The proc macros.
#[salsa::input]
fn proc_macros(&self) -> Arc<ProcMacros>;
}

View file

@ -50,6 +50,7 @@ pub enum FlycheckConfig {
extra_args: Vec<String>,
extra_env: FxHashMap<String, String>,
ansi_color_output: bool,
target_dir: Option<PathBuf>,
},
CustomCommand {
command: String,
@ -308,6 +309,7 @@ impl FlycheckActor {
features,
extra_env,
ansi_color_output,
target_dir,
} => {
let mut cmd = Command::new(toolchain::cargo());
cmd.arg(command);
@ -340,6 +342,9 @@ impl FlycheckActor {
cmd.arg(features.join(" "));
}
}
if let Some(target_dir) = target_dir {
cmd.arg("--target-dir").arg(target_dir);
}
cmd.envs(extra_env);
(cmd, extra_args)
}

View file

@ -12,7 +12,6 @@ rust-version.workspace = true
doctest = false
[dependencies]
anymap = "1.0.0-beta.2"
arrayvec = "0.7.2"
bitflags = "2.1.0"
cov-mark = "2.0.0-pre.1"
@ -31,10 +30,7 @@ smallvec.workspace = true
hashbrown.workspace = true
triomphe.workspace = true
rustc_abi.workspace = true
rustc_index.workspace = true
rustc_parse_format.workspace = true
rustc-dependencies.workspace = true
# local deps
stdx.workspace = true
@ -48,8 +44,12 @@ cfg.workspace = true
tt.workspace = true
limit.workspace = true
[dev-dependencies]
expect-test = "1.4.0"
# local deps
test-utils.workspace = true
[features]
in-rust-tree = ["rustc-dependencies/in-rust-tree"]

View file

@ -215,6 +215,10 @@ impl Attrs {
self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
}
pub fn export_name(&self) -> Option<&SmolStr> {
self.by_key("export_name").string_value()
}
pub fn is_proc_macro(&self) -> bool {
self.by_key("proc_macro").exists()
}

View file

@ -57,7 +57,7 @@ pub struct Body {
pub type ExprPtr = AstPtr<ast::Expr>;
pub type ExprSource = InFile<ExprPtr>;
pub type PatPtr = Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>;
pub type PatPtr = AstPtr<Either<ast::Pat, ast::SelfParam>>;
pub type PatSource = InFile<PatPtr>;
pub type LabelPtr = AstPtr<ast::Label>;
@ -356,12 +356,12 @@ impl BodySourceMap {
}
pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
let src = node.map(|it| Either::Left(AstPtr::new(it)));
let src = node.map(|it| AstPtr::new(it).wrap_left());
self.pat_map.get(&src).cloned()
}
pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option<PatId> {
let src = node.map(|it| Either::Right(AstPtr::new(it)));
let src = node.map(|it| AstPtr::new(it).wrap_right());
self.pat_map.get(&src).cloned()
}

View file

@ -196,16 +196,12 @@ impl ExprCollector<'_> {
if let Some(self_param) =
param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false))
{
let ptr = AstPtr::new(&self_param);
let binding_id: la_arena::Idx<Binding> = self.alloc_binding(
name![self],
BindingAnnotation::new(
self_param.mut_token().is_some() && self_param.amp_token().is_none(),
false,
),
);
let param_pat =
self.alloc_pat(Pat::Bind { id: binding_id, subpat: None }, Either::Right(ptr));
let is_mutable =
self_param.mut_token().is_some() && self_param.amp_token().is_none();
let ptr = AstPtr::new(&Either::Right(self_param));
let binding_id: la_arena::Idx<Binding> =
self.alloc_binding(name![self], BindingAnnotation::new(is_mutable, false));
let param_pat = self.alloc_pat(Pat::Bind { id: binding_id, subpat: None }, ptr);
self.add_definition_to_binding(binding_id, param_pat);
self.body.params.push(param_pat);
}
@ -1260,8 +1256,8 @@ impl ExprCollector<'_> {
(Some(id), Pat::Bind { id, subpat })
};
let ptr = AstPtr::new(&pat);
let pat = self.alloc_pat(pattern, Either::Left(ptr));
let ptr = AstPtr::new(&Either::Left(pat));
let pat = self.alloc_pat(pattern, ptr);
if let Some(binding_id) = binding {
self.add_definition_to_binding(binding_id, pat);
}
@ -1395,7 +1391,7 @@ impl ExprCollector<'_> {
ast::Pat::MacroPat(mac) => match mac.macro_call() {
Some(call) => {
let macro_ptr = AstPtr::new(&call);
let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
let src = self.expander.to_source(AstPtr::new(&Either::Left(pat)));
let pat =
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
this.collect_pat_opt(expanded_pat, binding_list)
@ -1430,8 +1426,8 @@ impl ExprCollector<'_> {
Pat::Range { start, end }
}
};
let ptr = AstPtr::new(&pat);
self.alloc_pat(pattern, Either::Left(ptr))
let ptr = AstPtr::new(&Either::Left(pat));
self.alloc_pat(pattern, ptr)
}
fn collect_pat_opt(&mut self, pat: Option<ast::Pat>, binding_list: &mut BindingList) -> PatId {

View file

@ -1,7 +1,6 @@
//! Name resolution for expressions.
use hir_expand::name::Name;
use la_arena::{Arena, Idx, IdxRange, RawIdx};
use rustc_hash::FxHashMap;
use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
use triomphe::Arc;
use crate::{
@ -17,7 +16,7 @@ pub type ScopeId = Idx<ScopeData>;
pub struct ExprScopes {
scopes: Arena<ScopeData>,
scope_entries: Arena<ScopeEntry>,
scope_by_expr: FxHashMap<ExprId, ScopeId>,
scope_by_expr: ArenaMap<ExprId, ScopeId>,
}
#[derive(Debug, PartialEq, Eq)]
@ -77,10 +76,10 @@ impl ExprScopes {
}
pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
self.scope_by_expr.get(&expr).copied()
self.scope_by_expr.get(expr).copied()
}
pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
pub fn scope_by_expr(&self) -> &ArenaMap<ExprId, ScopeId> {
&self.scope_by_expr
}
}
@ -94,7 +93,7 @@ impl ExprScopes {
let mut scopes = ExprScopes {
scopes: Arena::default(),
scope_entries: Arena::default(),
scope_by_expr: FxHashMap::default(),
scope_by_expr: ArenaMap::with_capacity(body.exprs.len()),
};
let mut root = scopes.root_scope();
scopes.add_params_bindings(body, root, &body.params);
@ -476,10 +475,7 @@ fn foo() {
.pat_syntax(*body.bindings[resolved.binding()].definitions.first().unwrap())
.unwrap();
let local_name = pat_src.value.either(
|it| it.syntax_node_ptr().to_node(file.syntax()),
|it| it.syntax_node_ptr().to_node(file.syntax()),
);
let local_name = pat_src.value.syntax_node_ptr().to_node(file.syntax());
assert_eq!(local_name.text_range(), expected_name.syntax().text_range());
}

View file

@ -15,9 +15,7 @@ use crate::{
attr::Attrs,
db::DefDatabase,
expander::{Expander, Mark},
item_tree::{
self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, Param, TreeId,
},
item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, TreeId},
macro_call_as_call_id, macro_id_to_def_id,
nameres::{
attr_resolution::ResolvedAttr,
@ -69,7 +67,7 @@ impl FunctionData {
let is_varargs = enabled_params
.clone()
.next_back()
.map_or(false, |param| matches!(item_tree[param], Param::Varargs));
.map_or(false, |param| item_tree[param].type_ref.is_none());
let mut flags = func.flags;
if is_varargs {
@ -105,10 +103,7 @@ impl FunctionData {
name: func.name.clone(),
params: enabled_params
.clone()
.filter_map(|id| match &item_tree[id] {
Param::Normal(ty) => Some(ty.clone()),
Param::Varargs => None,
})
.filter_map(|id| item_tree[id].type_ref.clone())
.collect(),
ret_type: func.ret_type.clone(),
attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),

View file

@ -11,7 +11,7 @@ use hir_expand::{
};
use intern::Interned;
use la_arena::{Arena, ArenaMap};
use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
use rustc_dependencies::abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
use syntax::ast::{self, HasName, HasVisibility};
use triomphe::Arc;

View file

@ -29,8 +29,8 @@ use std::{
ops::{Index, IndexMut},
};
use anymap::Map;
use rustc_hash::FxHashMap;
use stdx::anymap::Map;
pub struct Key<K, V, P = (K, V)> {
_phantom: PhantomData<(K, V, P)>,

View file

@ -2,7 +2,7 @@
use std::mem;
use hir_expand::name::Name;
use rustc_parse_format as parse;
use rustc_dependencies::parse_format as parse;
use syntax::{
ast::{self, IsString},
AstToken, SmolStr, TextRange,

View file

@ -1,7 +1,6 @@
//! A map of all publicly exported items in a crate.
use std::collections::hash_map::Entry;
use std::{fmt, hash::BuildHasherDefault};
use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault};
use base_db::CrateId;
use fst::{self, Streamer};
@ -11,10 +10,12 @@ use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use triomphe::Arc;
use crate::item_scope::ImportOrExternCrate;
use crate::{
db::DefDatabase, item_scope::ItemInNs, nameres::DefMap, visibility::Visibility, AssocItemId,
ModuleDefId, ModuleId, TraitId,
db::DefDatabase,
item_scope::{ImportOrExternCrate, ItemInNs},
nameres::DefMap,
visibility::Visibility,
AssocItemId, ModuleDefId, ModuleId, TraitId,
};
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@ -94,7 +95,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
// We look only into modules that are public(ly reexported), starting with the crate root.
let root = def_map.module_id(DefMap::ROOT);
let mut worklist = vec![(root, 0)];
let mut worklist = vec![(root, 0u32)];
// Records items' minimum module depth.
let mut depth_map = FxHashMap::default();
@ -278,6 +279,8 @@ enum SearchMode {
/// Import map entry should contain all letters from the query string,
/// in the same order, but not necessary adjacent.
Fuzzy,
/// Import map entry should match the query string by prefix.
Prefix,
}
/// Three possible ways to search for the name in associated and/or other items.
@ -319,6 +322,14 @@ impl Query {
Self { search_mode: SearchMode::Fuzzy, ..self }
}
pub fn prefix(self) -> Self {
Self { search_mode: SearchMode::Prefix, ..self }
}
pub fn exact(self) -> Self {
Self { search_mode: SearchMode::Exact, ..self }
}
/// Specifies whether we want to include associated items in the result.
pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
Self { assoc_mode, ..self }
@ -356,7 +367,8 @@ impl Query {
let query_string = if case_insensitive { &self.lowercased } else { &self.query };
match self.search_mode {
SearchMode::Exact => &input == query_string,
SearchMode::Exact => input == *query_string,
SearchMode::Prefix => input.starts_with(query_string),
SearchMode::Fuzzy => {
let mut input_chars = input.chars();
for query_char in query_string.chars() {

View file

@ -613,10 +613,17 @@ pub struct Function {
pub(crate) flags: FnFlags,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Param {
Normal(Interned<TypeRef>),
Varargs,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
/// This is [`None`] for varargs
pub type_ref: Option<Interned<TypeRef>>,
pub ast_id: ParamAstId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParamAstId {
Param(FileAstId<ast::Param>),
SelfParam(FileAstId<ast::SelfParam>),
}
bitflags::bitflags! {

View file

@ -295,8 +295,12 @@ impl<'a> Ctx<'a> {
}
}
};
let ty = Interned::new(self_type);
let idx = self.data().params.alloc(Param::Normal(ty));
let type_ref = Interned::new(self_type);
let ast_id = self.source_ast_id_map.ast_id(&self_param);
let idx = self.data().params.alloc(Param {
type_ref: Some(type_ref),
ast_id: ParamAstId::SelfParam(ast_id),
});
self.add_attrs(
idx.into(),
RawAttrs::new(self.db.upcast(), &self_param, self.hygiene()),
@ -305,11 +309,19 @@ impl<'a> Ctx<'a> {
}
for param in param_list.params() {
let idx = match param.dotdotdot_token() {
Some(_) => self.data().params.alloc(Param::Varargs),
Some(_) => {
let ast_id = self.source_ast_id_map.ast_id(&param);
self.data()
.params
.alloc(Param { type_ref: None, ast_id: ParamAstId::Param(ast_id) })
}
None => {
let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
let ty = Interned::new(type_ref);
self.data().params.alloc(Param::Normal(ty))
let ast_id = self.source_ast_id_map.ast_id(&param);
self.data()
.params
.alloc(Param { type_ref: Some(ty), ast_id: ParamAstId::Param(ast_id) })
}
};
self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), &param, self.hygiene()));

View file

@ -261,15 +261,15 @@ impl Printer<'_> {
self.indented(|this| {
for param in params.clone() {
this.print_attrs_of(param, "\n");
match &this.tree[param] {
Param::Normal(ty) => {
match &this.tree[param].type_ref {
Some(ty) => {
if flags.contains(FnFlags::HAS_SELF_PARAM) {
w!(this, "self: ");
}
this.print_type_ref(ty);
wln!(this, ",");
}
Param::Varargs => {
None => {
wln!(this, "...");
}
};

View file

@ -8,6 +8,7 @@
//! actually true.
#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[allow(unused)]
macro_rules! eprintln {
@ -48,7 +49,7 @@ pub mod visibility;
pub mod find_path;
pub mod import_map;
pub use rustc_abi as layout;
pub use rustc_dependencies::abi as layout;
use triomphe::Arc;
#[cfg(test)]
@ -72,6 +73,7 @@ use hir_expand::{
db::ExpandDatabase,
eager::expand_eager_macro_input,
hygiene::Hygiene,
name::Name,
proc_macro::ProcMacroExpander,
AstId, ExpandError, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind,
MacroDefId, MacroDefKind, UnresolvedMacro,
@ -173,6 +175,18 @@ impl ModuleId {
self.krate
}
pub fn name(self, db: &dyn db::DefDatabase) -> Option<Name> {
let def_map = self.def_map(db);
let parent = def_map[self.local_id].parent?;
def_map[parent].children.iter().find_map(|(name, module_id)| {
if *module_id == self.local_id {
Some(name.clone())
} else {
None
}
})
}
pub fn containing_module(self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
self.def_map(db).containing_module(self.local_id)
}
@ -498,10 +512,7 @@ impl_from!(Macro2Id, MacroRulesId, ProcMacroId for MacroId);
impl MacroId {
pub fn is_attribute(self, db: &dyn db::DefDatabase) -> bool {
match self {
MacroId::ProcMacroId(it) => it.lookup(db).kind == ProcMacroKind::Attr,
_ => false,
}
matches!(self, MacroId::ProcMacroId(it) if it.lookup(db).kind == ProcMacroKind::Attr)
}
}

View file

@ -99,7 +99,7 @@ register_ast_id_node! {
TraitAlias,
TypeAlias,
Use,
AssocItem, BlockExpr, Variant, RecordField, TupleField, ConstArg
AssocItem, BlockExpr, Variant, RecordField, TupleField, ConstArg, Param, SelfParam
}
/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.

View file

@ -12,11 +12,15 @@ use syntax::{
use triomphe::Arc;
use crate::{
ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion,
builtin_fn_macro::EagerExpander, fixup, hygiene::HygieneFrame, tt, AstId, BuiltinAttrExpander,
BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, ExpandError, ExpandResult,
ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId,
MacroDefKind, MacroFile, ProcMacroExpander,
ast_id_map::AstIdMap,
builtin_attr_macro::pseudo_derive_attr_expansion,
builtin_fn_macro::EagerExpander,
fixup,
hygiene::HygieneFrame,
name::{name, AsName},
tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo,
ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind,
MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
};
/// Total limit on the number of tokens produced by any macro invocation.
@ -614,9 +618,25 @@ fn macro_expand(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt
err = error.clone().or(err);
}
// Set a hard limit for the expanded tt
if let Err(value) = check_tt_count(&tt) {
return value;
// Skip checking token tree limit for include! macro call
let skip_check_tt_count = match loc.kind {
MacroCallKind::FnLike { ast_id, expand_to: _ } => {
if let Some(name_ref) =
ast_id.to_node(db).path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
{
name_ref.as_name() == name!(include)
} else {
false
}
}
_ => false,
};
if !skip_check_tt_count {
// Set a hard limit for the expanded tt
if let Err(value) = check_tt_count(&tt) {
return value;
}
}
ExpandResult { value: Arc::new(tt), err }

View file

@ -23,17 +23,17 @@ oorandom = "11.1.3"
tracing = "0.1.35"
rustc-hash = "1.1.0"
scoped-tls = "1.0.0"
chalk-solve = { version = "0.92.0", default-features = false }
chalk-ir = "0.92.0"
chalk-recursive = { version = "0.92.0", default-features = false }
chalk-derive = "0.92.0"
chalk-solve = { version = "0.93.0", default-features = false }
chalk-ir = "0.93.0"
chalk-recursive = { version = "0.93.0", default-features = false }
chalk-derive = "0.93.0"
la-arena.workspace = true
once_cell = "1.17.0"
triomphe.workspace = true
nohash-hasher.workspace = true
typed-arena = "2.0.1"
rustc_index.workspace = true
rustc-dependencies.workspace = true
# local deps
stdx.workspace = true
@ -56,3 +56,6 @@ project-model = { path = "../project-model" }
# local deps
test-utils.workspace = true
[features]
in-rust-tree = ["rustc-dependencies/in-rust-tree"]

View file

@ -1159,6 +1159,20 @@ fn pattern_matching_slice() {
"#,
33213,
);
check_number(
r#"
//- minicore: slice, index, coerce_unsized, copy
const fn f(mut slice: &[u32]) -> usize {
slice = match slice {
[0, rest @ ..] | rest => rest,
};
slice.len()
}
const GOAL: usize = f(&[]) + f(&[10]) + f(&[0, 100])
+ f(&[1000, 1000, 1000]) + f(&[0, 57, 34, 46, 10000, 10000]);
"#,
10,
);
}
#[test]

View file

@ -9,6 +9,7 @@
//! - constants (e.g. `const FOO: u8 = 10;`)
//! - static items (e.g. `static FOO: u8 = 10;`)
//! - match arm bindings (e.g. `foo @ Some(_)`)
//! - modules (e.g. `mod foo { ... }` or `mod foo;`)
mod case_conv;
@ -19,7 +20,7 @@ use hir_def::{
hir::{Pat, PatId},
src::HasSource,
AdtId, AttrDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, ItemContainerId,
Lookup, ModuleDefId, StaticId, StructId,
Lookup, ModuleDefId, ModuleId, StaticId, StructId,
};
use hir_expand::{
name::{AsName, Name},
@ -83,6 +84,7 @@ pub enum IdentType {
Structure,
Variable,
Variant,
Module,
}
impl fmt::Display for IdentType {
@ -97,6 +99,7 @@ impl fmt::Display for IdentType {
IdentType::Structure => "Structure",
IdentType::Variable => "Variable",
IdentType::Variant => "Variant",
IdentType::Module => "Module",
};
repr.fmt(f)
@ -132,6 +135,7 @@ impl<'a> DeclValidator<'a> {
pub(super) fn validate_item(&mut self, item: ModuleDefId) {
match item {
ModuleDefId::ModuleId(module_id) => self.validate_module(module_id),
ModuleDefId::FunctionId(func) => self.validate_func(func),
ModuleDefId::AdtId(adt) => self.validate_adt(adt),
ModuleDefId::ConstId(const_id) => self.validate_const(const_id),
@ -230,6 +234,55 @@ impl<'a> DeclValidator<'a> {
|| parent()
}
fn validate_module(&mut self, module_id: ModuleId) {
// Check whether non-snake case identifiers are allowed for this module.
if self.allowed(module_id.into(), allow::NON_SNAKE_CASE, false) {
return;
}
// Check the module name.
let Some(module_name) = module_id.name(self.db.upcast()) else { return };
let module_name_replacement =
module_name.as_str().and_then(to_lower_snake_case).map(|new_name| Replacement {
current_name: module_name,
suggested_text: new_name,
expected_case: CaseType::LowerSnakeCase,
});
if let Some(module_name_replacement) = module_name_replacement {
let module_data = &module_id.def_map(self.db.upcast())[module_id.local_id];
let module_src = module_data.declaration_source(self.db.upcast());
if let Some(module_src) = module_src {
let ast_ptr = match module_src.value.name() {
Some(name) => name,
None => {
never!(
"Replacement ({:?}) was generated for a module without a name: {:?}",
module_name_replacement,
module_src
);
return;
}
};
let diagnostic = IncorrectCase {
file: module_src.file_id,
ident_type: IdentType::Module,
ident: AstPtr::new(&ast_ptr),
expected_case: module_name_replacement.expected_case,
ident_text: module_name_replacement
.current_name
.display(self.db.upcast())
.to_string(),
suggested_text: module_name_replacement.suggested_text,
};
self.sink.push(diagnostic);
}
}
}
fn validate_func(&mut self, func: FunctionId) {
let data = self.db.function_data(func);
if matches!(func.lookup(self.db.upcast()).container, ItemContainerId::ExternBlockId(_)) {
@ -336,48 +389,44 @@ impl<'a> DeclValidator<'a> {
for (id, replacement) in pats_replacements {
if let Ok(source_ptr) = source_map.pat_syntax(id) {
if let Some(expr) = source_ptr.value.as_ref().left() {
if let Some(ptr) = source_ptr.value.clone().cast::<ast::IdentPat>() {
let root = source_ptr.file_syntax(self.db.upcast());
if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
let parent = match ident_pat.syntax().parent() {
Some(parent) => parent,
None => continue,
};
let name_ast = match ident_pat.name() {
Some(name_ast) => name_ast,
None => continue,
};
let ident_pat = ptr.to_node(&root);
let parent = match ident_pat.syntax().parent() {
Some(parent) => parent,
None => continue,
};
let name_ast = match ident_pat.name() {
Some(name_ast) => name_ast,
None => continue,
};
let is_param = ast::Param::can_cast(parent.kind());
let is_param = ast::Param::can_cast(parent.kind());
// We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
// because e.g. match arms are patterns as well.
// In other words, we check that it's a named variable binding.
let is_binding = ast::LetStmt::can_cast(parent.kind())
|| (ast::MatchArm::can_cast(parent.kind())
&& ident_pat.at_token().is_some());
if !(is_param || is_binding) {
// This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
continue;
}
let ident_type =
if is_param { IdentType::Parameter } else { IdentType::Variable };
let diagnostic = IncorrectCase {
file: source_ptr.file_id,
ident_type,
ident: AstPtr::new(&name_ast),
expected_case: replacement.expected_case,
ident_text: replacement
.current_name
.display(self.db.upcast())
.to_string(),
suggested_text: replacement.suggested_text,
};
self.sink.push(diagnostic);
// We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
// because e.g. match arms are patterns as well.
// In other words, we check that it's a named variable binding.
let is_binding = ast::LetStmt::can_cast(parent.kind())
|| (ast::MatchArm::can_cast(parent.kind())
&& ident_pat.at_token().is_some());
if !(is_param || is_binding) {
// This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
continue;
}
let ident_type =
if is_param { IdentType::Parameter } else { IdentType::Variable };
let diagnostic = IncorrectCase {
file: source_ptr.file_id,
ident_type,
ident: AstPtr::new(&name_ast),
expected_case: replacement.expected_case,
ident_text: replacement.current_name.display(self.db.upcast()).to_string(),
suggested_text: replacement.suggested_text,
};
self.sink.push(diagnostic);
}
}
}

View file

@ -11,50 +11,7 @@ pub(crate) fn to_camel_case(ident: &str) -> Option<String> {
return None;
}
// Taken from rustc.
let ret = ident
.trim_matches('_')
.split('_')
.filter(|component| !component.is_empty())
.map(|component| {
let mut camel_cased_component = String::with_capacity(component.len());
let mut new_word = true;
let mut prev_is_lower_case = true;
for c in component.chars() {
// Preserve the case if an uppercase letter follows a lowercase letter, so that
// `camelCase` is converted to `CamelCase`.
if prev_is_lower_case && c.is_uppercase() {
new_word = true;
}
if new_word {
camel_cased_component.extend(c.to_uppercase());
} else {
camel_cased_component.extend(c.to_lowercase());
}
prev_is_lower_case = c.is_lowercase();
new_word = false;
}
camel_cased_component
})
.fold((String::new(), None), |(acc, prev): (_, Option<String>), next| {
// separate two components with an underscore if their boundary cannot
// be distinguished using an uppercase/lowercase case distinction
let join = prev
.and_then(|prev| {
let f = next.chars().next()?;
let l = prev.chars().last()?;
Some(!char_has_case(l) && !char_has_case(f))
})
.unwrap_or(false);
(acc + if join { "_" } else { "" } + &next, Some(next))
})
.0;
Some(ret)
Some(stdx::to_camel_case(ident))
}
/// Converts an identifier to a lower_snake_case form.
@ -97,7 +54,9 @@ fn is_camel_case(name: &str) -> bool {
&& !name.chars().any(|snd| {
let ret = match fst {
None => false,
Some(fst) => char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_',
Some(fst) => {
stdx::char_has_case(fst) && snd == '_' || stdx::char_has_case(snd) && fst == '_'
}
};
fst = Some(snd);
@ -135,11 +94,6 @@ fn is_snake_case<F: Fn(char) -> bool>(ident: &str, wrong_case: F) -> bool {
})
}
// Taken from rustc.
fn char_has_case(c: char) -> bool {
c.is_lowercase() || c.is_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -147,7 +147,7 @@ impl<'a> PatCtxt<'a> {
}
hir_def::hir::Pat::Bind { id, subpat, .. } => {
let bm = self.infer.binding_modes[id];
let bm = self.infer.binding_modes[pat];
ty = &self.infer[id];
let name = &self.body.bindings[id].name;
match (bm, ty.kind(Interner)) {

View file

@ -420,7 +420,19 @@ pub struct InferenceResult {
standard_types: InternedStandardTypes,
/// Stores the types which were implicitly dereferenced in pattern binding modes.
pub pat_adjustments: FxHashMap<PatId, Vec<Ty>>,
pub binding_modes: ArenaMap<BindingId, BindingMode>,
/// Stores the binding mode (`ref` in `let ref x = 2`) of bindings.
///
/// This one is tied to the `PatId` instead of `BindingId`, because in some rare cases, a binding in an
/// or pattern can have multiple binding modes. For example:
/// ```
/// fn foo(mut slice: &[u32]) -> usize {
/// slice = match slice {
/// [0, rest @ ..] | rest => rest,
/// };
/// }
/// ```
/// the first `rest` has implicit `ref` binding mode, but the second `rest` binding mode is `move`.
pub binding_modes: ArenaMap<PatId, BindingMode>,
pub expr_adjustments: FxHashMap<ExprId, Vec<Adjustment>>,
pub(crate) closure_info: FxHashMap<ClosureId, (Vec<CapturedItem>, FnTrait)>,
// FIXME: remove this field

View file

@ -679,7 +679,7 @@ impl InferenceContext<'_> {
| Pat::Range { .. } => {
update_result(CaptureKind::ByRef(BorrowKind::Shared));
}
Pat::Bind { id, .. } => match self.result.binding_modes[*id] {
Pat::Bind { id, .. } => match self.result.binding_modes[p] {
crate::BindingMode::Move => {
if self.is_ty_copy(self.result.type_of_binding[*id].clone()) {
update_result(CaptureKind::ByRef(BorrowKind::Shared));
@ -838,8 +838,8 @@ impl InferenceContext<'_> {
| Pat::ConstBlock(_)
| Pat::Path(_)
| Pat::Lit(_) => self.consume_place(place, pat.into()),
Pat::Bind { id, subpat: _ } => {
let mode = self.result.binding_modes[*id];
Pat::Bind { id: _, subpat: _ } => {
let mode = self.result.binding_modes[pat];
let capture_kind = match mode {
BindingMode::Move => {
self.consume_place(place, pat.into());

View file

@ -421,7 +421,7 @@ impl InferenceContext<'_> {
} else {
BindingMode::convert(mode)
};
self.result.binding_modes.insert(binding, mode);
self.result.binding_modes.insert(pat, mode);
let inner_ty = match subpat {
Some(subpat) => self.infer_pat(subpat, &expected, default_bm),

View file

@ -9,6 +9,10 @@ use hir_def::{
LocalEnumVariantId, LocalFieldId, StructId,
};
use la_arena::{Idx, RawIdx};
use rustc_dependencies::{
abi::AddressSpace,
index::{IndexSlice, IndexVec},
};
use stdx::never;
use triomphe::Arc;
@ -34,7 +38,7 @@ mod target;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RustcEnumVariantIdx(pub LocalEnumVariantId);
impl rustc_index::vec::Idx for RustcEnumVariantIdx {
impl rustc_dependencies::index::Idx for RustcEnumVariantIdx {
fn new(idx: usize) -> Self {
RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32)))
}
@ -44,9 +48,28 @@ impl rustc_index::vec::Idx for RustcEnumVariantIdx {
}
}
pub type Layout = LayoutS<RustcEnumVariantIdx>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RustcFieldIdx(pub LocalFieldId);
impl RustcFieldIdx {
pub fn new(idx: usize) -> Self {
RustcFieldIdx(Idx::from_raw(RawIdx::from(idx as u32)))
}
}
impl rustc_dependencies::index::Idx for RustcFieldIdx {
fn new(idx: usize) -> Self {
RustcFieldIdx(Idx::from_raw(RawIdx::from(idx as u32)))
}
fn index(self) -> usize {
u32::from(self.0.into_raw()) as usize
}
}
pub type Layout = LayoutS<RustcFieldIdx, RustcEnumVariantIdx>;
pub type TagEncoding = hir_def::layout::TagEncoding<RustcEnumVariantIdx>;
pub type Variants = hir_def::layout::Variants<RustcEnumVariantIdx>;
pub type Variants = hir_def::layout::Variants<RustcFieldIdx, RustcEnumVariantIdx>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum LayoutError {
@ -66,7 +89,7 @@ struct LayoutCx<'a> {
impl<'a> LayoutCalculator for LayoutCx<'a> {
type TargetDataLayoutRef = &'a TargetDataLayout;
fn delay_bug(&self, txt: &str) {
fn delay_bug(&self, txt: String) {
never!("{}", txt);
}
@ -145,6 +168,8 @@ fn layout_of_simd_ty(
largest_niche: e_ly.largest_niche,
size,
align,
max_repr_align: None,
unadjusted_abi_align: align.abi,
}))
}
@ -230,7 +255,7 @@ pub fn layout_of_ty_query(
.map(|k| db.layout_of_ty(k.assert_ty_ref(Interner).clone(), trait_env.clone()))
.collect::<Result<Vec<_>, _>>()?;
let fields = fields.iter().map(|it| &**it).collect::<Vec<_>>();
let fields = fields.iter().collect::<Vec<_>>();
let fields = fields.iter().collect::<IndexVec<_, _>>();
cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)?
}
TyKind::Array(element, count) => {
@ -255,6 +280,8 @@ pub fn layout_of_ty_query(
largest_niche,
align: element.align,
size,
max_repr_align: None,
unadjusted_abi_align: element.align.abi,
}
}
TyKind::Slice(element) => {
@ -266,11 +293,23 @@ pub fn layout_of_ty_query(
largest_niche: None,
align: element.align,
size: Size::ZERO,
max_repr_align: None,
unadjusted_abi_align: element.align.abi,
}
}
TyKind::Str => Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: Size::from_bytes(1), count: 0 },
abi: Abi::Aggregate { sized: false },
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
max_repr_align: None,
unadjusted_abi_align: dl.i8_align.abi,
},
// Potentially-wide pointers.
TyKind::Ref(_, _, pointee) | TyKind::Raw(_, pointee) => {
let mut data_ptr = scalar_unit(dl, Primitive::Pointer);
let mut data_ptr = scalar_unit(dl, Primitive::Pointer(AddressSpace::DATA));
if matches!(ty.kind(Interner), TyKind::Ref(..)) {
data_ptr.valid_range_mut().start = 1;
}
@ -294,7 +333,7 @@ pub fn layout_of_ty_query(
scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false))
}
TyKind::Dyn(..) => {
let mut vtable = scalar_unit(dl, Primitive::Pointer);
let mut vtable = scalar_unit(dl, Primitive::Pointer(AddressSpace::DATA));
vtable.valid_range_mut().start = 1;
vtable
}
@ -308,22 +347,7 @@ pub fn layout_of_ty_query(
cx.scalar_pair(data_ptr, metadata)
}
TyKind::FnDef(_, _) => layout_of_unit(&cx, dl)?,
TyKind::Str => Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: Size::from_bytes(1), count: 0 },
abi: Abi::Aggregate { sized: false },
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
},
TyKind::Never => Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Primitive,
abi: Abi::Uninhabited,
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
},
TyKind::Never => cx.layout_of_never_type(),
TyKind::Dyn(_) | TyKind::Foreign(_) => {
let mut unit = layout_of_unit(&cx, dl)?;
match unit.abi {
@ -333,7 +357,7 @@ pub fn layout_of_ty_query(
unit
}
TyKind::Function(_) => {
let mut ptr = scalar_unit(dl, Primitive::Pointer);
let mut ptr = scalar_unit(dl, Primitive::Pointer(dl.instruction_address_space));
ptr.valid_range_mut().start = 1;
Layout::scalar(dl, ptr)
}
@ -363,7 +387,7 @@ pub fn layout_of_ty_query(
})
.collect::<Result<Vec<_>, _>>()?;
let fields = fields.iter().map(|it| &**it).collect::<Vec<_>>();
let fields = fields.iter().collect::<Vec<_>>();
let fields = fields.iter().collect::<IndexVec<_, _>>();
cx.univariant(dl, &fields, &ReprOptions::default(), StructKind::AlwaysSized)
.ok_or(LayoutError::Unknown)?
}
@ -398,9 +422,9 @@ pub fn layout_of_ty_recover(
}
fn layout_of_unit(cx: &LayoutCx<'_>, dl: &TargetDataLayout) -> Result<Layout, LayoutError> {
cx.univariant::<RustcEnumVariantIdx, &&Layout>(
cx.univariant::<RustcFieldIdx, RustcEnumVariantIdx, &&Layout>(
dl,
&[],
IndexSlice::empty(),
&ReprOptions::default(),
StructKind::AlwaysSized,
)

View file

@ -8,6 +8,7 @@ use hir_def::{
AdtId, EnumVariantId, LocalEnumVariantId, VariantId,
};
use la_arena::RawIdx;
use rustc_dependencies::index::IndexVec;
use smallvec::SmallVec;
use triomphe::Arc;
@ -20,8 +21,8 @@ use crate::{
use super::LayoutCx;
pub(crate) fn struct_variant_idx() -> RustcEnumVariantIdx {
RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from(0)))
pub(crate) const fn struct_variant_idx() -> RustcEnumVariantIdx {
RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from_u32(0)))
}
pub fn layout_of_adt_query(
@ -74,7 +75,7 @@ pub fn layout_of_adt_query(
.iter()
.map(|it| it.iter().map(|it| &**it).collect::<Vec<_>>())
.collect::<SmallVec<[_; 1]>>();
let variants = variants.iter().map(|it| it.iter().collect()).collect();
let variants = variants.iter().map(|it| it.iter().collect()).collect::<IndexVec<_, _>>();
let result = if matches!(def, AdtId::UnionId(..)) {
cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown)?
} else {
@ -105,7 +106,7 @@ pub fn layout_of_adt_query(
&& variants
.iter()
.next()
.and_then(|it| it.last().map(|it| !it.is_unsized()))
.and_then(|it| it.iter().last().map(|it| !it.is_unsized()))
.unwrap_or(true),
)
.ok_or(LayoutError::SizeOverflow)?
@ -119,7 +120,15 @@ fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>,
let attr = attrs.by_key(name).tt_values();
for tree in attr {
if let Some(it) = tree.token_trees.first() {
if let Ok(it) = it.to_string().parse() {
let text = it.to_string().replace('_', "");
let (text, base) = match text.as_bytes() {
[b'0', b'x', ..] => (&text[2..], 16),
[b'0', b'o', ..] => (&text[2..], 8),
[b'0', b'b', ..] => (&text[2..], 2),
_ => (&*text, 10),
};
if let Ok(it) = u128::from_str_radix(text, base) {
return Bound::Included(it);
}
}

View file

@ -186,9 +186,9 @@ fn capture_specific_fields() {
fn match_pattern() {
size_and_align_expr! {
struct X(i64, i32, (u8, i128));
let y: X = X(2, 5, (7, 3));
let _y: X = X(2, 5, (7, 3));
move |x: i64| {
match y {
match _y {
_ => x,
}
}

View file

@ -243,16 +243,16 @@ impl Default for ProjectionStore {
}
impl ProjectionStore {
fn shrink_to_fit(&mut self) {
pub fn shrink_to_fit(&mut self) {
self.id_to_proj.shrink_to_fit();
self.proj_to_id.shrink_to_fit();
}
fn intern_if_exist(&self, projection: &[PlaceElem]) -> Option<ProjectionId> {
pub fn intern_if_exist(&self, projection: &[PlaceElem]) -> Option<ProjectionId> {
self.proj_to_id.get(projection).copied()
}
fn intern(&mut self, projection: Box<[PlaceElem]>) -> ProjectionId {
pub fn intern(&mut self, projection: Box<[PlaceElem]>) -> ProjectionId {
let new_id = ProjectionId(self.proj_to_id.len() as u32);
match self.proj_to_id.entry(projection) {
Entry::Occupied(id) => *id.get(),
@ -267,20 +267,20 @@ impl ProjectionStore {
}
impl ProjectionId {
const EMPTY: ProjectionId = ProjectionId(0);
pub const EMPTY: ProjectionId = ProjectionId(0);
fn lookup(self, store: &ProjectionStore) -> &[PlaceElem] {
pub fn lookup(self, store: &ProjectionStore) -> &[PlaceElem] {
store.id_to_proj.get(&self).unwrap()
}
fn project(self, projection: PlaceElem, store: &mut ProjectionStore) -> ProjectionId {
pub fn project(self, projection: PlaceElem, store: &mut ProjectionStore) -> ProjectionId {
let mut current = self.lookup(store).to_vec();
current.push(projection);
store.intern(current.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Place {
pub local: LocalId,
pub projection: ProjectionId,
@ -1007,7 +1007,7 @@ pub enum Rvalue {
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum StatementKind {
Assign(Place, Rvalue),
//FakeRead(Box<(FakeReadCause, Place)>),
FakeRead(Place),
//SetDiscriminant {
// place: Box<Place>,
// variant_index: VariantIdx,
@ -1109,7 +1109,9 @@ impl MirBody {
}
}
}
StatementKind::Deinit(p) => f(p, &mut self.projection_store),
StatementKind::FakeRead(p) | StatementKind::Deinit(p) => {
f(p, &mut self.projection_store)
}
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Nop => (),

View file

@ -24,6 +24,7 @@ use super::{
pub enum MutabilityReason {
Mut { spans: Vec<MirSpan> },
Not,
Unused,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -144,7 +145,8 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
}
}
},
StatementKind::Deinit(_)
StatementKind::FakeRead(_)
| StatementKind::Deinit(_)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Nop => (),
@ -264,7 +266,10 @@ fn ever_initialized_map(
is_ever_initialized = false;
}
}
StatementKind::Deinit(_) | StatementKind::Nop | StatementKind::StorageLive(_) => (),
StatementKind::Deinit(_)
| StatementKind::FakeRead(_)
| StatementKind::Nop
| StatementKind::StorageLive(_) => (),
}
}
let Some(terminator) = &block.terminator else {
@ -331,16 +336,37 @@ fn ever_initialized_map(
result
}
fn push_mut_span(local: LocalId, span: MirSpan, result: &mut ArenaMap<LocalId, MutabilityReason>) {
match &mut result[local] {
MutabilityReason::Mut { spans } => spans.push(span),
it @ (MutabilityReason::Not | MutabilityReason::Unused) => {
*it = MutabilityReason::Mut { spans: vec![span] }
}
};
}
fn record_usage(local: LocalId, result: &mut ArenaMap<LocalId, MutabilityReason>) {
match &mut result[local] {
it @ MutabilityReason::Unused => {
*it = MutabilityReason::Not;
}
_ => (),
};
}
fn record_usage_for_operand(arg: &Operand, result: &mut ArenaMap<LocalId, MutabilityReason>) {
if let Operand::Copy(p) | Operand::Move(p) = arg {
record_usage(p.local, result);
}
}
fn mutability_of_locals(
db: &dyn HirDatabase,
body: &MirBody,
) -> ArenaMap<LocalId, MutabilityReason> {
let mut result: ArenaMap<LocalId, MutabilityReason> =
body.locals.iter().map(|it| (it.0, MutabilityReason::Not)).collect();
let mut push_mut_span = |local, span| match &mut result[local] {
MutabilityReason::Mut { spans } => spans.push(span),
it @ MutabilityReason::Not => *it = MutabilityReason::Mut { spans: vec![span] },
};
body.locals.iter().map(|it| (it.0, MutabilityReason::Unused)).collect();
let ever_init_maps = ever_initialized_map(db, body);
for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
let block = &body.basic_blocks[block_id];
@ -350,23 +376,51 @@ fn mutability_of_locals(
match place_case(db, body, place) {
ProjectionCase::Direct => {
if ever_init_map.get(place.local).copied().unwrap_or_default() {
push_mut_span(place.local, statement.span);
push_mut_span(place.local, statement.span, &mut result);
} else {
ever_init_map.insert(place.local, true);
}
}
ProjectionCase::DirectPart => {
// Partial initialization is not supported, so it is definitely `mut`
push_mut_span(place.local, statement.span);
push_mut_span(place.local, statement.span, &mut result);
}
ProjectionCase::Indirect => (),
ProjectionCase::Indirect => {
record_usage(place.local, &mut result);
}
}
match value {
Rvalue::CopyForDeref(p)
| Rvalue::Discriminant(p)
| Rvalue::Len(p)
| Rvalue::Ref(_, p) => {
record_usage(p.local, &mut result);
}
Rvalue::Use(o)
| Rvalue::Repeat(o, _)
| Rvalue::Cast(_, o, _)
| Rvalue::UnaryOp(_, o) => record_usage_for_operand(o, &mut result),
Rvalue::CheckedBinaryOp(_, o1, o2) => {
for o in [o1, o2] {
record_usage_for_operand(o, &mut result);
}
}
Rvalue::Aggregate(_, args) => {
for arg in args.iter() {
record_usage_for_operand(arg, &mut result);
}
}
Rvalue::ShallowInitBox(_, _) | Rvalue::ShallowInitBoxWithAlloc(_) => (),
}
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
if place_case(db, body, p) != ProjectionCase::Indirect {
push_mut_span(p.local, statement.span);
push_mut_span(p.local, statement.span, &mut result);
}
}
}
StatementKind::FakeRead(p) => {
record_usage(p.local, &mut result);
}
StatementKind::StorageDead(p) => {
ever_init_map.insert(*p, false);
}
@ -386,15 +440,21 @@ fn mutability_of_locals(
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::Yield { .. } => (),
TerminatorKind::Call { destination, .. } => {
TerminatorKind::SwitchInt { discr, targets: _ } => {
record_usage_for_operand(discr, &mut result);
}
TerminatorKind::Call { destination, args, func, .. } => {
record_usage_for_operand(func, &mut result);
for arg in args.iter() {
record_usage_for_operand(arg, &mut result);
}
if destination.projection.lookup(&body.projection_store).len() == 0 {
if ever_init_map.get(destination.local).copied().unwrap_or_default() {
push_mut_span(destination.local, MirSpan::Unknown);
push_mut_span(destination.local, MirSpan::Unknown, &mut result);
} else {
ever_init_map.insert(destination.local, true);
}

View file

@ -162,7 +162,7 @@ pub struct Evaluator<'a> {
not_special_fn_cache: RefCell<FxHashSet<FunctionId>>,
mir_or_dyn_index_cache: RefCell<FxHashMap<(FunctionId, Substitution), MirOrDynIndex>>,
/// Constantly dropping and creating `Locals` is very costly. We store
/// old locals that we normaly want to drop here, to reuse their allocations
/// old locals that we normally want to drop here, to reuse their allocations
/// later.
unused_locals_store: RefCell<FxHashMap<DefWithBodyId, Vec<Locals>>>,
cached_ptr_size: usize,
@ -375,10 +375,7 @@ impl MirEvalError {
Err(_) => continue,
},
MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
Ok(s) => s.map(|it| match it {
Either::Left(e) => e.into(),
Either::Right(e) => e.into(),
}),
Ok(s) => s.map(|it| it.syntax_node_ptr()),
Err(_) => continue,
},
MirSpan::Unknown => continue,
@ -842,6 +839,7 @@ impl Evaluator<'_> {
}
StatementKind::Deinit(_) => not_supported!("de-init statement"),
StatementKind::StorageLive(_)
| StatementKind::FakeRead(_)
| StatementKind::StorageDead(_)
| StatementKind::Nop => (),
}
@ -2301,7 +2299,7 @@ impl Evaluator<'_> {
match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
MirOrDynIndex::Dyn(self_ty_idx) => {
// In the layout of current possible receiver, which at the moment of writing this code is one of
// `&T`, `&mut T`, `Box<T>`, `Rc<T>`, `Arc<T>`, and `Pin<P>` where `P` is one of possible recievers,
// `&T`, `&mut T`, `Box<T>`, `Rc<T>`, `Arc<T>`, and `Pin<P>` where `P` is one of possible receivers,
// the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on
// the type.
let first_arg = arg_bytes.clone().next().unwrap();

View file

@ -1045,7 +1045,7 @@ impl Evaluator<'_> {
}
"transmute" => {
let [arg] = args else {
return Err(MirEvalError::TypeError("trasmute arg is not provided"));
return Err(MirEvalError::TypeError("transmute arg is not provided"));
};
destination.write_from_interval(self, arg.interval)
}
@ -1065,7 +1065,7 @@ impl Evaluator<'_> {
}
"ctlz" | "ctlz_nonzero" => {
let [arg] = args else {
return Err(MirEvalError::TypeError("cttz arg is not provided"));
return Err(MirEvalError::TypeError("ctlz arg is not provided"));
};
let result =
u128::from_le_bytes(pad16(arg.get(self)?, false)).leading_zeros() as usize;

View file

@ -529,6 +529,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
else {
return Ok(None);
};
self.push_fake_read(current, cond_place, expr_id.into());
let (then_target, else_target) =
self.pattern_match(current, None, cond_place, *pat)?;
self.write_bytes_to_place(
@ -668,6 +669,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
else {
return Ok(None);
};
self.push_fake_read(current, cond_place, expr_id.into());
let mut end = None;
for MatchArm { pat, guard, expr } in arms.iter() {
let (then, mut otherwise) =
@ -1299,6 +1301,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
return Ok(None);
};
if matches!(&self.body.exprs[lhs], Expr::Underscore) {
self.push_fake_read_for_operand(current, rhs_op, span);
return Ok(Some(current));
}
if matches!(
@ -1575,6 +1578,16 @@ impl<'ctx> MirLowerCtx<'ctx> {
self.result.basic_blocks[block].statements.push(statement);
}
fn push_fake_read(&mut self, block: BasicBlockId, p: Place, span: MirSpan) {
self.push_statement(block, StatementKind::FakeRead(p).with_span(span));
}
fn push_fake_read_for_operand(&mut self, block: BasicBlockId, operand: Operand, span: MirSpan) {
if let Operand::Move(p) | Operand::Copy(p) = operand {
self.push_fake_read(block, p, span);
}
}
fn push_assignment(
&mut self,
block: BasicBlockId,
@ -1733,6 +1746,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
return Ok(None);
};
current = c;
self.push_fake_read(current, init_place, span);
(current, else_block) =
self.pattern_match(current, None, init_place, *pat)?;
match (else_block, else_branch) {
@ -1760,13 +1774,14 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
}
}
hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
&hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
let scope2 = self.push_drop_scope();
let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else {
let Some((p, c)) = self.lower_expr_as_place(current, expr, true)? else {
scope2.pop_assume_dropped(self);
scope.pop_assume_dropped(self);
return Ok(None);
};
self.push_fake_read(c, p, expr.into());
current = scope2.pop_and_drop(self, c);
}
}

View file

@ -284,6 +284,7 @@ impl MirLowerCtx<'_> {
);
(current, current_else) = self.pattern_match_binding(
id,
*slice,
next_place,
(*slice).into(),
current,
@ -395,6 +396,7 @@ impl MirLowerCtx<'_> {
if mode == MatchingMode::Bind {
self.pattern_match_binding(
*id,
pattern,
cond_place,
pattern.into(),
current,
@ -431,13 +433,14 @@ impl MirLowerCtx<'_> {
fn pattern_match_binding(
&mut self,
id: BindingId,
pat: PatId,
cond_place: Place,
span: MirSpan,
current: BasicBlockId,
current_else: Option<BasicBlockId>,
) -> Result<(BasicBlockId, Option<BasicBlockId>)> {
let target_place = self.binding_local(id)?;
let mode = self.infer.binding_modes[id];
let mode = self.infer.binding_modes[pat];
self.push_storage_live(id, current)?;
self.push_assignment(
current,

View file

@ -248,6 +248,7 @@ impl Filler<'_> {
| Rvalue::CopyForDeref(_) => (),
},
StatementKind::Deinit(_)
| StatementKind::FakeRead(_)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Nop => (),

View file

@ -233,6 +233,11 @@ impl<'a> MirPrettyCtx<'a> {
this.place(p);
wln!(this, ");");
}
StatementKind::FakeRead(p) => {
w!(this, "FakeRead(");
this.place(p);
wln!(this, ");");
}
StatementKind::Nop => wln!(this, "Nop;"),
}
}

View file

@ -269,12 +269,7 @@ fn pat_node(
Some(match body_source_map.pat_syntax(pat) {
Ok(sp) => {
let root = db.parse_or_expand(sp.file_id);
sp.map(|ptr| {
ptr.either(
|it| it.to_node(&root).syntax().clone(),
|it| it.to_node(&root).syntax().clone(),
)
})
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
}
Err(SyntheticSyntax) => return None,
})
@ -303,12 +298,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
let syntax_ptr = match body_source_map.pat_syntax(pat) {
Ok(sp) => {
let root = db.parse_or_expand(sp.file_id);
sp.map(|ptr| {
ptr.either(
|it| it.to_node(&root).syntax().clone(),
|it| it.to_node(&root).syntax().clone(),
)
})
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
}
Err(SyntheticSyntax) => continue,
};

View file

@ -66,6 +66,7 @@ diagnostics![
UnresolvedModule,
UnresolvedProcMacro,
UnusedMut,
UnusedVariable,
];
#[derive(Debug)]
@ -173,20 +174,19 @@ pub struct MalformedDerive {
#[derive(Debug)]
pub struct NoSuchField {
pub field: InFile<Either<AstPtr<ast::RecordExprField>, AstPtr<ast::RecordPatField>>>,
pub field: InFile<AstPtr<Either<ast::RecordExprField, ast::RecordPatField>>>,
pub private: bool,
}
#[derive(Debug)]
pub struct PrivateAssocItem {
pub expr_or_pat:
InFile<Either<AstPtr<ast::Expr>, Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>>>,
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, Either<ast::Pat, ast::SelfParam>>>>,
pub item: AssocItem,
}
#[derive(Debug)]
pub struct MismatchedTupleStructPatArgCount {
pub expr_or_pat: InFile<Either<AstPtr<ast::Expr>, AstPtr<ast::Pat>>>,
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
pub expected: usize,
pub found: usize,
}
@ -227,7 +227,7 @@ pub struct MissingUnsafe {
#[derive(Debug)]
pub struct MissingFields {
pub file: HirFileId,
pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
pub field_list_parent: AstPtr<Either<ast::RecordExpr, ast::RecordPat>>,
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
pub missed_fields: Vec<Name>,
}
@ -254,7 +254,7 @@ pub struct MissingMatchArms {
#[derive(Debug)]
pub struct TypeMismatch {
pub expr_or_pat: Either<InFile<AstPtr<ast::Expr>>, InFile<AstPtr<ast::Pat>>>,
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
pub expected: Type,
pub actual: Type,
}
@ -270,6 +270,11 @@ pub struct UnusedMut {
pub local: Local,
}
#[derive(Debug)]
pub struct UnusedVariable {
pub local: Local,
}
#[derive(Debug)]
pub struct MovedOutOfRef {
pub ty: Type,

View file

@ -64,7 +64,7 @@ use hir_ty::{
consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt},
diagnostics::BodyValidationDiagnostic,
known_const_to_ast,
layout::{Layout as TyLayout, RustcEnumVariantIdx, TagEncoding},
layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding},
method_resolution::{self, TyFingerprint},
mir::{self, interpret_mir},
primitive::UintTy,
@ -98,7 +98,7 @@ pub use crate::{
ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
UnresolvedProcMacro, UnusedMut,
UnresolvedProcMacro, UnusedMut, UnusedVariable,
},
has_source::HasSource,
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@ -452,15 +452,7 @@ impl HasVisibility for ModuleDef {
impl Module {
/// Name of this module.
pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
let def_map = self.id.def_map(db.upcast());
let parent = def_map[self.id.local_id].parent?;
def_map[parent].children.iter().find_map(|(name, module_id)| {
if *module_id == self.id.local_id {
Some(name.clone())
} else {
None
}
})
self.id.name(db.upcast())
}
/// Returns the crate this module is part of.
@ -571,6 +563,7 @@ impl Module {
if def_map[m.id.local_id].origin.is_inline() {
m.diagnostics(db, acc)
}
acc.extend(def.diagnostics(db))
}
ModuleDef::Trait(t) => {
for diag in db.trait_data_with_diagnostics(t.id).1.iter() {
@ -1509,10 +1502,10 @@ impl DefWithBody {
&hir_ty::InferenceDiagnostic::NoSuchField { field: expr, private } => {
let expr_or_pat = match expr {
ExprOrPatId::ExprId(expr) => {
source_map.field_syntax(expr).map(Either::Left)
source_map.field_syntax(expr).map(AstPtr::wrap_left)
}
ExprOrPatId::PatId(pat) => {
source_map.pat_field_syntax(pat).map(Either::Right)
source_map.pat_field_syntax(pat).map(AstPtr::wrap_right)
}
};
acc.push(NoSuchField { field: expr_or_pat, private }.into())
@ -1530,8 +1523,8 @@ impl DefWithBody {
}
&hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => {
let expr_or_pat = match id {
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left),
ExprOrPatId::PatId(pat) => pat_syntax(pat).map(Either::Right),
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(AstPtr::wrap_left),
ExprOrPatId::PatId(pat) => pat_syntax(pat).map(AstPtr::wrap_right),
};
let item = item.into();
acc.push(PrivateAssocItem { expr_or_pat, item }.into())
@ -1609,12 +1602,17 @@ impl DefWithBody {
found,
} => {
let expr_or_pat = match pat {
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left),
ExprOrPatId::PatId(pat) => source_map
.pat_syntax(pat)
.expect("unexpected synthetic")
.map(|it| it.unwrap_left())
.map(Either::Right),
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(AstPtr::wrap_left),
ExprOrPatId::PatId(pat) => {
let InFile { file_id, value } =
source_map.pat_syntax(pat).expect("unexpected synthetic");
// cast from Either<Pat, SelfParam> -> Either<_, Pat>
let Some(ptr) = AstPtr::try_from_raw(value.syntax_node_ptr()) else {
continue;
};
InFile { file_id, value: ptr }
}
};
acc.push(
MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into(),
@ -1628,11 +1626,15 @@ impl DefWithBody {
ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right),
};
let expr_or_pat = match expr_or_pat {
Ok(Either::Left(expr)) => Either::Left(expr),
Ok(Either::Right(InFile { file_id, value: Either::Left(pat) })) => {
Either::Right(InFile { file_id, value: pat })
Ok(Either::Left(expr)) => expr.map(AstPtr::wrap_left),
Ok(Either::Right(InFile { file_id, value: pat })) => {
// cast from Either<Pat, SelfParam> -> Either<_, Pat>
let Some(ptr) = AstPtr::try_from_raw(pat.syntax_node_ptr()) else {
continue;
};
InFile { file_id, value: ptr }
}
Ok(Either::Right(_)) | Err(SyntheticSyntax) => continue,
Err(SyntheticSyntax) => continue,
};
acc.push(
@ -1667,10 +1669,7 @@ impl DefWithBody {
Err(_) => continue,
},
mir::MirSpan::PatId(p) => match source_map.pat_syntax(p) {
Ok(s) => s.map(|it| match it {
Either::Left(e) => e.into(),
Either::Right(e) => e.into(),
}),
Ok(s) => s.map(|it| it.into()),
Err(_) => continue,
},
mir::MirSpan::Unknown => continue,
@ -1697,9 +1696,20 @@ impl DefWithBody {
// Skip synthetic bindings
continue;
}
let need_mut = &mol[local];
let mut need_mut = &mol[local];
if body[binding_id].name.as_str() == Some("self")
&& need_mut == &mir::MutabilityReason::Unused
{
need_mut = &mir::MutabilityReason::Not;
}
let local = Local { parent: self.into(), binding_id };
match (need_mut, local.is_mut(db)) {
(mir::MutabilityReason::Unused, _) => {
let should_ignore = matches!(body[binding_id].name.as_str(), Some(it) if it.starts_with("_"));
if !should_ignore {
acc.push(UnusedVariable { local }.into())
}
}
(mir::MutabilityReason::Mut { .. }, true)
| (mir::MutabilityReason::Not, false) => (),
(mir::MutabilityReason::Mut { spans }, false) => {
@ -1710,10 +1720,7 @@ impl DefWithBody {
Err(_) => continue,
},
mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
Ok(s) => s.map(|it| match it {
Either::Left(e) => e.into(),
Either::Right(e) => e.into(),
}),
Ok(s) => s.map(|it| it.into()),
Err(_) => continue,
},
mir::MirSpan::Unknown => continue,
@ -1752,18 +1759,18 @@ impl DefWithBody {
Ok(source_ptr) => {
let root = source_ptr.file_syntax(db.upcast());
if let ast::Expr::RecordExpr(record_expr) =
&source_ptr.value.to_node(&root)
source_ptr.value.to_node(&root)
{
if record_expr.record_expr_field_list().is_some() {
let field_list_parent_path =
record_expr.path().map(|path| AstPtr::new(&path));
acc.push(
MissingFields {
file: source_ptr.file_id,
field_list_parent: Either::Left(AstPtr::new(
field_list_parent: AstPtr::new(&Either::Left(
record_expr,
)),
field_list_parent_path: record_expr
.path()
.map(|path| AstPtr::new(&path)),
field_list_parent_path,
missed_fields,
}
.into(),
@ -1775,24 +1782,24 @@ impl DefWithBody {
},
Either::Right(record_pat) => match source_map.pat_syntax(record_pat) {
Ok(source_ptr) => {
if let Some(expr) = source_ptr.value.as_ref().left() {
if let Some(ptr) = source_ptr.value.clone().cast::<ast::RecordPat>()
{
let root = source_ptr.file_syntax(db.upcast());
if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
if record_pat.record_pat_field_list().is_some() {
acc.push(
MissingFields {
file: source_ptr.file_id,
field_list_parent: Either::Right(AstPtr::new(
&record_pat,
)),
field_list_parent_path: record_pat
.path()
.map(|path| AstPtr::new(&path)),
missed_fields,
}
.into(),
)
}
let record_pat = ptr.to_node(&root);
if record_pat.record_pat_field_list().is_some() {
let field_list_parent_path =
record_pat.path().map(|path| AstPtr::new(&path));
acc.push(
MissingFields {
file: source_ptr.file_id,
field_list_parent: AstPtr::new(&Either::Right(
record_pat,
)),
field_list_parent_path,
missed_fields,
}
.into(),
)
}
}
}
@ -1960,6 +1967,17 @@ impl Function {
db.function_data(self.id).attrs.is_test()
}
/// is this a `fn main` or a function with an `export_name` of `main`?
pub fn is_main(self, db: &dyn HirDatabase) -> bool {
if !self.module(db).is_crate_root() {
return false;
}
let data = db.function_data(self.id);
data.name.to_smol_str() == "main"
|| data.attrs.export_name().map(core::ops::Deref::deref) == Some("main")
}
/// Does this function have the ignore attribute?
pub fn is_ignore(self, db: &dyn HirDatabase) -> bool {
db.function_data(self.id).attrs.is_ignore()
@ -2926,10 +2944,10 @@ impl Local {
.map(|&definition| {
let src = source_map.pat_syntax(definition).unwrap(); // Hmm...
let root = src.file_syntax(db.upcast());
src.map(|ast| match ast {
// Suspicious unwrap
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
Either::Right(it) => Either::Right(it.to_node(&root)),
src.map(|ast| match ast.to_node(&root) {
Either::Left(ast::Pat::IdentPat(it)) => Either::Left(it),
Either::Left(_) => unreachable!("local with non ident-pattern"),
Either::Right(it) => Either::Right(it),
})
})
.map(move |source| LocalSource { local: self, source })
@ -4515,15 +4533,31 @@ impl Layout {
Some(self.0.largest_niche?.available(&*self.1))
}
pub fn field_offset(&self, idx: usize) -> Option<u64> {
pub fn field_offset(&self, field: Field) -> Option<u64> {
match self.0.fields {
layout::FieldsShape::Primitive => None,
layout::FieldsShape::Union(_) => Some(0),
layout::FieldsShape::Array { stride, count } => {
let i = u64::try_from(idx).ok()?;
let i = u64::try_from(field.index()).ok()?;
(i < count).then_some((stride * i).bytes())
}
layout::FieldsShape::Arbitrary { ref offsets, .. } => Some(offsets.get(idx)?.bytes()),
layout::FieldsShape::Arbitrary { ref offsets, .. } => {
Some(offsets.get(RustcFieldIdx(field.id))?.bytes())
}
}
}
pub fn tuple_field_offset(&self, field: usize) -> Option<u64> {
match self.0.fields {
layout::FieldsShape::Primitive => None,
layout::FieldsShape::Union(_) => Some(0),
layout::FieldsShape::Array { stride, count } => {
let i = u64::try_from(field).ok()?;
(i < count).then_some((stride * i).bytes())
}
layout::FieldsShape::Arbitrary { ref offsets, .. } => {
Some(offsets.get(RustcFieldIdx::new(field))?.bytes())
}
}
}

View file

@ -236,9 +236,9 @@ impl SourceAnalyzer {
_db: &dyn HirDatabase,
pat: &ast::IdentPat,
) -> Option<BindingMode> {
let binding_id = self.binding_id_of_pat(pat)?;
let id = self.pat_id(&pat.clone().into())?;
let infer = self.infer.as_ref()?;
infer.binding_modes.get(binding_id).map(|bm| match bm {
infer.binding_modes.get(id).map(|bm| match bm {
hir_ty::BindingMode::Move => BindingMode::Move,
hir_ty::BindingMode::Ref(hir_ty::Mutability::Mut) => BindingMode::Ref(Mutability::Mut),
hir_ty::BindingMode::Ref(hir_ty::Mutability::Not) => {
@ -888,7 +888,7 @@ fn scope_for_offset(
.scope_by_expr()
.iter()
.filter_map(|(id, scope)| {
let InFile { file_id, value } = source_map.expr_syntax(*id).ok()?;
let InFile { file_id, value } = source_map.expr_syntax(id).ok()?;
if from_file == file_id {
return Some((value.text_range(), scope));
}
@ -923,7 +923,7 @@ fn adjust(
.scope_by_expr()
.iter()
.filter_map(|(id, scope)| {
let source = source_map.expr_syntax(*id).ok()?;
let source = source_map.expr_syntax(id).ok()?;
// FIXME: correctly handle macro expansion
if source.file_id != from_file {
return None;

View file

@ -273,9 +273,10 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
syntax::SyntaxElement::Token(it) => {
// Don't have a way to make tokens mut, so instead make the parent mut
// and find the token again
let parent = edit.make_syntax_mut(it.parent().unwrap());
let parent =
edit.make_syntax_mut(it.parent().expect("Token must have a parent."));
let mut_token =
parent.covering_element(it.text_range()).into_token().unwrap();
parent.covering_element(it.text_range()).into_token().expect("Covering element cannot be found. Range may be beyond the current node's range");
syntax::SyntaxElement::from(mut_token)
}
@ -446,21 +447,23 @@ fn build_pat(
mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var), prefer_no_std)?);
// FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
let pat: ast::Pat = match var.source(db)?.value.kind() {
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());
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().unwrap()).into());
let pats = field_list.fields().map(|f| {
make::ext::simple_ident_pat(
f.name().expect("Record field must have a name"),
)
.into()
});
make::record_pat(path, pats).into()
}
ast::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"))),
@ -1941,4 +1944,35 @@ fn main() {
"#,
);
}
/// See [`discussion`](https://github.com/rust-lang/rust-analyzer/pull/15594#discussion_r1322960614)
#[test]
fn missing_field_name() {
check_assist(
add_missing_match_arms,
r#"
enum A {
A,
Missing { a: u32, : u32, c: u32 }
}
fn a() {
let b = A::A;
match b$0 {}
}"#,
r#"
enum A {
A,
Missing { a: u32, : u32, c: u32 }
}
fn a() {
let b = A::A;
match b {
$0A::A => todo!(),
A::Missing { a, u32, c } => todo!(),
}
}"#,
)
}
}

View file

@ -1,7 +1,13 @@
use std::collections::VecDeque;
use ide_db::{
assists::GroupLabel,
famous_defs::FamousDefs,
source_change::SourceChangeBuilder,
syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
};
use syntax::{
ast::{self, AstNode, Expr::BinExpr},
ast::{self, make, AstNode, Expr::BinExpr, HasArgList},
ted::{self, Position},
SyntaxKind,
};
@ -89,7 +95,8 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
let dm_lhs = demorganed.lhs()?;
acc.add(
acc.add_group(
&GroupLabel("Apply De Morgan's law".to_string()),
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
"Apply De Morgan's law",
op_range,
@ -143,6 +150,127 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
)
}
// Assist: apply_demorgan_iterator
//
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law] to
// `Iterator::all` and `Iterator::any`.
//
// This transforms expressions of the form `!iter.any(|x| predicate(x))` into
// `iter.all(|x| !predicate(x))` and vice versa. This also works the other way for
// `Iterator::all` into `Iterator::any`.
//
// ```
// # //- minicore: iterator
// fn main() {
// let arr = [1, 2, 3];
// if !arr.into_iter().$0any(|num| num == 4) {
// println!("foo");
// }
// }
// ```
// ->
// ```
// fn main() {
// let arr = [1, 2, 3];
// if arr.into_iter().all(|num| num != 4) {
// println!("foo");
// }
// }
// ```
pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?;
let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None };
let closure_body = closure_expr.body()?;
let op_range = method_call.syntax().text_range();
let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str());
acc.add_group(
&GroupLabel("Apply De Morgan's law".to_string()),
AssistId("apply_demorgan_iterator", AssistKind::RefactorRewrite),
label,
op_range,
|edit| {
// replace the method name
let new_name = match name.text().as_str() {
"all" => make::name_ref("any"),
"any" => make::name_ref("all"),
_ => unreachable!(),
}
.clone_for_update();
edit.replace_ast(name, new_name);
// negate all tail expressions in the closure body
let tail_cb = &mut |e: &_| tail_cb_impl(edit, e);
walk_expr(&closure_body, &mut |expr| {
if let ast::Expr::ReturnExpr(ret_expr) = expr {
if let Some(ret_expr_arg) = &ret_expr.expr() {
for_each_tail_expr(ret_expr_arg, tail_cb);
}
}
});
for_each_tail_expr(&closure_body, tail_cb);
// negate the whole method call
if let Some(prefix_expr) = method_call
.syntax()
.parent()
.and_then(ast::PrefixExpr::cast)
.filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
{
edit.delete(
prefix_expr
.op_token()
.expect("prefix expression always has an operator")
.text_range(),
);
} else {
edit.insert(method_call.syntax().text_range().start(), "!");
}
},
)
}
/// Ensures that the method call is to `Iterator::all` or `Iterator::any`.
fn validate_method_call_expr(
ctx: &AssistContext<'_>,
method_call: &ast::MethodCallExpr,
) -> Option<(ast::NameRef, ast::Expr)> {
let name_ref = method_call.name_ref()?;
if name_ref.text() != "all" && name_ref.text() != "any" {
return None;
}
let arg_expr = method_call.arg_list()?.args().next()?;
let sema = &ctx.sema;
let receiver = method_call.receiver()?;
let it_type = sema.type_of_expr(&receiver)?.adjusted();
let module = sema.scope(receiver.syntax())?.module();
let krate = module.krate();
let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
it_type.impls_trait(sema.db, iter_trait, &[]).then_some((name_ref, arg_expr))
}
fn tail_cb_impl(edit: &mut SourceChangeBuilder, e: &ast::Expr) {
match e {
ast::Expr::BreakExpr(break_expr) => {
if let Some(break_expr_arg) = break_expr.expr() {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(edit, e))
}
}
ast::Expr::ReturnExpr(_) => {
// all return expressions have already been handled by the walk loop
}
e => {
let inverted_body = invert_boolean_expression(e.clone());
edit.replace(e.syntax().text_range(), inverted_body.syntax().text());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -255,4 +383,206 @@ fn f() { !(S <= S || S < S) }
"fn() { let x = a && b && c; }",
)
}
#[test]
fn demorgan_iterator_any_all_reverse() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().all(|num| num $0!= 4) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().any(|num| num == 4) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_all_any() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0all(|num| num > 3) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().any(|num| num <= 3) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_multiple_terms() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0any(|num| num > 3 && num == 23 && num <= 30) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().all(|num| !(num > 3 && num == 23 && num <= 30)) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_double_negation() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0all(|num| !(num > 3)) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().any(|num| num > 3) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_double_parens() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0any(|num| (num > 3 && (num == 1 || num == 2))) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().all(|num| !(num > 3 && (num == 1 || num == 2))) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_multiline() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if arr
.into_iter()
.all$0(|num| !num.is_negative())
{
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [1, 2, 3];
if !arr
.into_iter()
.any(|num| num.is_negative())
{
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_block_closure() {
check_assist(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [-1, 1, 2, 3];
if arr.into_iter().all(|num: i32| {
$0if num.is_positive() {
num <= 3
} else {
num >= -1
}
}) {
println!("foo");
}
}
"#,
r#"
fn main() {
let arr = [-1, 1, 2, 3];
if !arr.into_iter().any(|num: i32| {
if num.is_positive() {
num > 3
} else {
num < -1
}
}) {
println!("foo");
}
}
"#,
);
}
#[test]
fn demorgan_iterator_wrong_method() {
check_assist_not_applicable(
apply_demorgan_iterator,
r#"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0map(|num| num > 3) {
println!("foo");
}
}
"#,
);
}
}

View file

@ -5,7 +5,7 @@ use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::{ImportAssets, ImportCandidate, LocatedImport},
insert_use::{insert_use, ImportScope},
insert_use::{insert_use, insert_use_as_alias, ImportScope},
},
};
use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
@ -129,10 +129,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
for import in proposed_imports {
let import_path = import.import_path;
let (assist_id, import_name) =
(AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db()));
acc.add_group(
&group_label,
AssistId("auto_import", AssistKind::QuickFix),
format!("Import `{}`", import_path.display(ctx.db())),
assist_id,
format!("Import `{}`", import_name),
range,
|builder| {
let scope = match scope.clone() {
@ -143,6 +145,38 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use);
},
);
match import_assets.import_candidate() {
ImportCandidate::TraitAssocItem(name) | ImportCandidate::TraitMethod(name) => {
let is_method =
matches!(import_assets.import_candidate(), ImportCandidate::TraitMethod(_));
let type_ = if is_method { "method" } else { "item" };
let group_label = GroupLabel(format!(
"Import a trait for {} {} by alias",
type_,
name.assoc_item_name.text()
));
acc.add_group(
&group_label,
assist_id,
format!("Import `{} as _`", import_name),
range,
|builder| {
let scope = match scope.clone() {
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
};
insert_use_as_alias(
&scope,
mod_path_to_ast(&import_path),
&ctx.config.insert_use,
);
},
);
}
_ => {}
}
}
Some(())
}
@ -253,7 +287,8 @@ mod tests {
};
use crate::tests::{
check_assist, check_assist_not_applicable, check_assist_target, TEST_CONFIG,
check_assist, check_assist_by_label, check_assist_not_applicable, check_assist_target,
TEST_CONFIG,
};
fn check_auto_import_order(before: &str, order: &[&str]) {
@ -705,7 +740,7 @@ fn main() {
#[test]
fn associated_trait_function() {
check_assist(
check_assist_by_label(
auto_import,
r"
mod test_mod {
@ -739,6 +774,44 @@ fn main() {
test_mod::TestStruct::test_function
}
",
"Import `test_mod::TestTrait`",
);
check_assist_by_label(
auto_import,
r"
mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
fn main() {
test_mod::TestStruct::test_function$0
}
",
r"
use test_mod::TestTrait as _;
mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
fn main() {
test_mod::TestStruct::test_function
}
",
"Import `test_mod::TestTrait as _`",
);
}
@ -776,7 +849,44 @@ fn main() {
#[test]
fn associated_trait_const() {
check_assist(
check_assist_by_label(
auto_import,
r"
mod test_mod {
pub trait TestTrait {
const TEST_CONST: u8;
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const TEST_CONST: u8 = 42;
}
}
fn main() {
test_mod::TestStruct::TEST_CONST$0
}
",
r"
use test_mod::TestTrait as _;
mod test_mod {
pub trait TestTrait {
const TEST_CONST: u8;
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const TEST_CONST: u8 = 42;
}
}
fn main() {
test_mod::TestStruct::TEST_CONST
}
",
"Import `test_mod::TestTrait as _`",
);
check_assist_by_label(
auto_import,
r"
mod test_mod {
@ -810,6 +920,7 @@ fn main() {
test_mod::TestStruct::TEST_CONST
}
",
"Import `test_mod::TestTrait`",
);
}
@ -847,7 +958,46 @@ fn main() {
#[test]
fn trait_method() {
check_assist(
check_assist_by_label(
auto_import,
r"
mod test_mod {
pub trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
fn main() {
let test_struct = test_mod::TestStruct {};
test_struct.test_meth$0od()
}
",
r"
use test_mod::TestTrait as _;
mod test_mod {
pub trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
fn main() {
let test_struct = test_mod::TestStruct {};
test_struct.test_method()
}
",
"Import `test_mod::TestTrait as _`",
);
check_assist_by_label(
auto_import,
r"
mod test_mod {
@ -883,12 +1033,43 @@ fn main() {
test_struct.test_method()
}
",
"Import `test_mod::TestTrait`",
);
}
#[test]
fn trait_method_cross_crate() {
check_assist(
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_meth$0od()
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
",
r"
use dep::test_mod::TestTrait as _;
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_method()
}
",
"Import `dep::test_mod::TestTrait as _`",
);
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@ -915,12 +1096,41 @@ fn main() {
test_struct.test_method()
}
",
"Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_fn_cross_crate() {
check_assist(
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::test_func$0tion
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
",
r"
use dep::test_mod::TestTrait as _;
fn main() {
dep::test_mod::TestStruct::test_function
}
",
"Import `dep::test_mod::TestTrait as _`",
);
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@ -945,12 +1155,41 @@ fn main() {
dep::test_mod::TestStruct::test_function
}
",
"Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_const_cross_crate() {
check_assist(
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::CONST$0
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
const CONST: bool;
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const CONST: bool = true;
}
}
",
r"
use dep::test_mod::TestTrait as _;
fn main() {
dep::test_mod::TestStruct::CONST
}
",
"Import `dep::test_mod::TestTrait as _`",
);
check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@ -975,6 +1214,7 @@ fn main() {
dep::test_mod::TestStruct::CONST
}
",
"Import `dep::test_mod::TestTrait`",
);
}

File diff suppressed because it is too large Load diff

View file

@ -25,9 +25,7 @@ pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_>)
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
// Only allow comments which are alone on their line
if let Some(prev) = comment.syntax().prev_token() {
if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
return None;
}
Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
}
match comment.kind().shape {
@ -78,7 +76,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
// Establish the target of our edit based on the comments we found
let target = TextRange::new(
comments[0].syntax().text_range().start(),
comments.last().unwrap().syntax().text_range().end(),
comments.last()?.syntax().text_range().end(),
);
acc.add(
@ -91,8 +89,12 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
// contents of each line comment when they're put into the block comment.
let indentation = IndentLevel::from_token(comment.syntax());
let block_comment_body =
comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n");
let block_comment_body = comments
.into_iter()
.map(|c| line_comment_text(indentation, c))
.collect::<Vec<String>>()
.into_iter()
.join("\n");
let block_prefix =
CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
@ -160,7 +162,8 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
//
// But since such comments aren't idiomatic we're okay with this.
pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
let text = comm.text();
let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text);
let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
// Don't add the indentation if the line is empty

View file

@ -51,22 +51,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
// Check if there is an IfLet that we can handle.
let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
let let_ = single_let(cond)?;
match let_.pat() {
Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
let path = pat.path()?;
if path.qualifier().is_some() {
return None;
}
let bound_ident = pat.fields().next()?;
if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
return None;
}
(Some((path, bound_ident)), let_.expr()?)
}
_ => return None, // Unsupported IfLet.
}
(Some(let_.pat()?), let_.expr()?)
} else {
(None, cond)
};
@ -136,11 +121,10 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
};
new_expr.syntax().clone_for_update()
}
Some((path, bound_ident)) => {
Some(pat) => {
// If-let.
let pat = make::tuple_struct_pat(path, once(bound_ident));
let let_else_stmt = make::let_else_stmt(
pat.into(),
pat,
None,
cond_expr,
ast::make::tail_only_block_expr(early_expression),
@ -442,6 +426,60 @@ fn main() {
);
}
#[test]
fn convert_arbitrary_if_let_patterns() {
check_assist(
convert_to_guarded_return,
r#"
fn main() {
$0if let None = Some(92) {
foo();
}
}
"#,
r#"
fn main() {
let None = Some(92) else { return };
foo();
}
"#,
);
check_assist(
convert_to_guarded_return,
r#"
fn main() {
$0if let [1, x] = [1, 92] {
foo(x);
}
}
"#,
r#"
fn main() {
let [1, x] = [1, 92] else { return };
foo(x);
}
"#,
);
check_assist(
convert_to_guarded_return,
r#"
fn main() {
$0if let (Some(x), None) = (Some(92), None) {
foo(x);
}
}
"#,
r#"
fn main() {
let (Some(x), None) = (Some(92), None) else { return };
foo(x);
}
"#,
);
}
#[test]
fn ignore_already_converted_if() {
check_assist_not_applicable(

View file

@ -0,0 +1,886 @@
use either::Either;
use hir::ModuleDef;
use ide_db::{
assists::{AssistId, AssistKind},
defs::Definition,
helpers::mod_path_to_ast,
imports::insert_use::{insert_use, ImportScope},
search::{FileReference, UsageSearchResult},
source_change::SourceChangeBuilder,
syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
FxHashSet,
};
use syntax::{
ast::{self, edit::IndentLevel, edit_in_place::Indent, make, HasName},
match_ast, ted, AstNode, SyntaxNode,
};
use crate::assist_context::{AssistContext, Assists};
// Assist: convert_tuple_return_type_to_struct
//
// This converts the return type of a function from a tuple type
// into a tuple struct and updates the body accordingly.
//
// ```
// fn bar() {
// let (a, b, c) = foo();
// }
//
// fn foo() -> ($0u32, u32, u32) {
// (1, 2, 3)
// }
// ```
// ->
// ```
// fn bar() {
// let FooResult(a, b, c) = foo();
// }
//
// struct FooResult(u32, u32, u32);
//
// fn foo() -> FooResult {
// FooResult(1, 2, 3)
// }
// ```
pub(crate) fn convert_tuple_return_type_to_struct(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
let type_ref = ret_type.ty()?;
let ast::Type::TupleType(tuple_ty) = &type_ref else { return None };
if tuple_ty.fields().any(|field| matches!(field, ast::Type::ImplTraitType(_))) {
return None;
}
let fn_ = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
let fn_def = ctx.sema.to_def(&fn_)?;
let fn_name = fn_.name()?;
let target_module = ctx.sema.scope(fn_.syntax())?.module().nearest_non_block_module(ctx.db());
let target = type_ref.syntax().text_range();
acc.add(
AssistId("convert_tuple_return_type_to_struct", AssistKind::RefactorRewrite),
"Convert tuple return type to tuple struct",
target,
move |edit| {
let ret_type = edit.make_mut(ret_type);
let fn_ = edit.make_mut(fn_);
let usages = Definition::Function(fn_def).usages(&ctx.sema).all();
let struct_name = format!("{}Result", stdx::to_camel_case(&fn_name.to_string()));
let parent = fn_.syntax().ancestors().find_map(<Either<ast::Impl, ast::Trait>>::cast);
add_tuple_struct_def(
edit,
ctx,
&usages,
parent.as_ref().map(|it| it.syntax()).unwrap_or(fn_.syntax()),
tuple_ty,
&struct_name,
&target_module,
);
ted::replace(
ret_type.syntax(),
make::ret_type(make::ty(&struct_name)).syntax().clone_for_update(),
);
if let Some(fn_body) = fn_.body() {
replace_body_return_values(ast::Expr::BlockExpr(fn_body), &struct_name);
}
replace_usages(edit, ctx, &usages, &struct_name, &target_module);
},
)
}
/// Replaces tuple usages with the corresponding tuple struct pattern.
fn replace_usages(
edit: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
usages: &UsageSearchResult,
struct_name: &str,
target_module: &hir::Module,
) {
for (file_id, references) in usages.iter() {
edit.edit_file(*file_id);
let refs_with_imports =
augment_references_with_imports(edit, ctx, references, struct_name, target_module);
refs_with_imports.into_iter().rev().for_each(|(name, import_data)| {
if let Some(fn_) = name.syntax().parent().and_then(ast::Fn::cast) {
cov_mark::hit!(replace_trait_impl_fns);
if let Some(ret_type) = fn_.ret_type() {
ted::replace(
ret_type.syntax(),
make::ret_type(make::ty(struct_name)).syntax().clone_for_update(),
);
}
if let Some(fn_body) = fn_.body() {
replace_body_return_values(ast::Expr::BlockExpr(fn_body), struct_name);
}
} else {
// replace tuple patterns
let pats = name
.syntax()
.ancestors()
.find(|node| {
ast::CallExpr::can_cast(node.kind())
|| ast::MethodCallExpr::can_cast(node.kind())
})
.and_then(|node| node.parent())
.and_then(node_to_pats)
.unwrap_or(Vec::new());
let tuple_pats = pats.iter().filter_map(|pat| match pat {
ast::Pat::TuplePat(tuple_pat) => Some(tuple_pat),
_ => None,
});
for tuple_pat in tuple_pats {
ted::replace(
tuple_pat.syntax(),
make::tuple_struct_pat(
make::path_from_text(struct_name),
tuple_pat.fields(),
)
.clone_for_update()
.syntax(),
);
}
}
// add imports across modules where needed
if let Some((import_scope, path)) = import_data {
insert_use(&import_scope, path, &ctx.config.insert_use);
}
})
}
}
fn node_to_pats(node: SyntaxNode) -> Option<Vec<ast::Pat>> {
match_ast! {
match node {
ast::LetStmt(it) => it.pat().map(|pat| vec![pat]),
ast::LetExpr(it) => it.pat().map(|pat| vec![pat]),
ast::MatchExpr(it) => it.match_arm_list().map(|arm_list| {
arm_list.arms().filter_map(|arm| arm.pat()).collect()
}),
_ => None,
}
}
}
fn augment_references_with_imports(
edit: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
references: &[FileReference],
struct_name: &str,
target_module: &hir::Module,
) -> Vec<(ast::NameLike, Option<(ImportScope, ast::Path)>)> {
let mut visited_modules = FxHashSet::default();
references
.iter()
.filter_map(|FileReference { name, .. }| {
ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module()))
})
.map(|(name, ref_module)| {
let new_name = edit.make_mut(name.clone());
// if the referenced module is not the same as the target one and has not been seen before, add an import
let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module
&& !visited_modules.contains(&ref_module)
{
visited_modules.insert(ref_module);
let import_scope =
ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema);
let path = ref_module
.find_use_path_prefixed(
ctx.sema.db,
ModuleDef::Module(*target_module),
ctx.config.insert_use.prefix_kind,
ctx.config.prefer_no_std,
)
.map(|mod_path| {
make::path_concat(
mod_path_to_ast(&mod_path),
make::path_from_text(struct_name),
)
});
import_scope.zip(path)
} else {
None
};
(new_name, import_data)
})
.collect()
}
// Adds the definition of the tuple struct before the parent function.
fn add_tuple_struct_def(
edit: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
usages: &UsageSearchResult,
parent: &SyntaxNode,
tuple_ty: &ast::TupleType,
struct_name: &str,
target_module: &hir::Module,
) {
let make_struct_pub = usages
.iter()
.flat_map(|(_, refs)| refs)
.filter_map(|FileReference { name, .. }| {
ctx.sema.scope(name.syntax()).map(|scope| scope.module())
})
.any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
let visibility = if make_struct_pub { Some(make::visibility_pub()) } else { None };
let field_list = ast::FieldList::TupleFieldList(make::tuple_field_list(
tuple_ty.fields().map(|ty| make::tuple_field(visibility.clone(), ty)),
));
let struct_name = make::name(struct_name);
let struct_def = make::struct_(visibility, struct_name, None, field_list).clone_for_update();
let indent = IndentLevel::from_node(parent);
struct_def.reindent_to(indent);
edit.insert(parent.text_range().start(), format!("{struct_def}\n\n{indent}"));
}
/// Replaces each returned tuple in `body` with the constructor of the tuple struct named `struct_name`.
fn replace_body_return_values(body: ast::Expr, struct_name: &str) {
let mut exprs_to_wrap = Vec::new();
let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
walk_expr(&body, &mut |expr| {
if let ast::Expr::ReturnExpr(ret_expr) = expr {
if let Some(ret_expr_arg) = &ret_expr.expr() {
for_each_tail_expr(ret_expr_arg, tail_cb);
}
}
});
for_each_tail_expr(&body, tail_cb);
for ret_expr in exprs_to_wrap {
if let ast::Expr::TupleExpr(tuple_expr) = &ret_expr {
let struct_constructor = make::expr_call(
make::expr_path(make::ext::ident_path(struct_name)),
make::arg_list(tuple_expr.fields()),
)
.clone_for_update();
ted::replace(ret_expr.syntax(), struct_constructor.syntax());
}
}
}
fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
match e {
ast::Expr::BreakExpr(break_expr) => {
if let Some(break_expr_arg) = break_expr.expr() {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
}
}
ast::Expr::ReturnExpr(_) => {
// all return expressions have already been handled by the walk loop
}
e => acc.push(e.clone()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_not_applicable};
#[test]
fn function_basic() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(&'static str, bool) {
("bar", true)
}
"#,
r#"
struct BarResult(&'static str, bool);
fn bar() -> BarResult {
BarResult("bar", true)
}
"#,
)
}
#[test]
fn struct_and_usages_indented() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
mod foo {
pub(crate) fn foo() {
let (bar, baz) = bar();
println!("{bar} {baz}");
}
pub(crate) fn bar() -> $0(usize, bool) {
(42, true)
}
}
"#,
r#"
mod foo {
pub(crate) fn foo() {
let BarResult(bar, baz) = bar();
println!("{bar} {baz}");
}
struct BarResult(usize, bool);
pub(crate) fn bar() -> BarResult {
BarResult(42, true)
}
}
"#,
)
}
#[test]
fn field_usage() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, bool) {
(42, true)
}
fn main() {
let bar_result = bar();
println!("{} {}", bar_result.1, bar().0);
}
"#,
r#"
struct BarResult(usize, bool);
fn bar() -> BarResult {
BarResult(42, true)
}
fn main() {
let bar_result = bar();
println!("{} {}", bar_result.1, bar().0);
}
"#,
)
}
#[test]
fn method_usage() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
struct Foo;
impl Foo {
fn foo(&self, x: usize) -> $0(usize, usize) {
(x, x)
}
}
fn main() {
let foo = Foo {};
let (x, y) = foo.foo(2);
}
"#,
r#"
struct Foo;
struct FooResult(usize, usize);
impl Foo {
fn foo(&self, x: usize) -> FooResult {
FooResult(x, x)
}
}
fn main() {
let foo = Foo {};
let FooResult(x, y) = foo.foo(2);
}
"#,
)
}
#[test]
fn method_usage_within_same_impl() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
struct Foo;
impl Foo {
fn new() -> $0(usize, usize) {
(0, 0)
}
fn foo() {
let (mut foo1, mut foo2) = Self::new();
}
}
"#,
r#"
struct Foo;
struct NewResult(usize, usize);
impl Foo {
fn new() -> NewResult {
NewResult(0, 0)
}
fn foo() {
let NewResult(mut foo1, mut foo2) = Self::new();
}
}
"#,
)
}
#[test]
fn multiple_usages() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, usize) {
(42, 24)
}
fn main() {
let bar_result = bar();
let (foo, b) = bar();
let (b, baz) = bar();
if foo == b && b == baz {
println!("{} {}", bar_result.1, bar().0);
}
}
"#,
r#"
struct BarResult(usize, usize);
fn bar() -> BarResult {
BarResult(42, 24)
}
fn main() {
let bar_result = bar();
let BarResult(foo, b) = bar();
let BarResult(b, baz) = bar();
if foo == b && b == baz {
println!("{} {}", bar_result.1, bar().0);
}
}
"#,
)
}
#[test]
fn usage_match_tuple_pat() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, bool) {
(42, true)
}
fn main() {
match bar() {
x if x.0 == 0 => println!("0"),
(x, false) => println!("{x}"),
(42, true) => println!("bar"),
_ => println!("foo"),
}
}
"#,
r#"
struct BarResult(usize, bool);
fn bar() -> BarResult {
BarResult(42, true)
}
fn main() {
match bar() {
x if x.0 == 0 => println!("0"),
BarResult(x, false) => println!("{x}"),
BarResult(42, true) => println!("bar"),
_ => println!("foo"),
}
}
"#,
)
}
#[test]
fn usage_if_let_tuple_pat() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, bool) {
(42, true)
}
fn main() {
if let (42, true) = bar() {
println!("bar")
}
}
"#,
r#"
struct BarResult(usize, bool);
fn bar() -> BarResult {
BarResult(42, true)
}
fn main() {
if let BarResult(42, true) = bar() {
println!("bar")
}
}
"#,
)
}
#[test]
fn function_nested_outer() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, bool) {
fn foo() -> (usize, bool) {
(42, true)
}
foo()
}
"#,
r#"
struct BarResult(usize, bool);
fn bar() -> BarResult {
fn foo() -> (usize, bool) {
(42, true)
}
foo()
}
"#,
)
}
#[test]
fn function_nested_inner() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> (usize, bool) {
fn foo() -> $0(usize, bool) {
(42, true)
}
foo()
}
"#,
r#"
fn bar() -> (usize, bool) {
struct FooResult(usize, bool);
fn foo() -> FooResult {
FooResult(42, true)
}
foo()
}
"#,
)
}
#[test]
fn trait_impl_and_usage() {
cov_mark::check!(replace_trait_impl_fns);
check_assist(
convert_tuple_return_type_to_struct,
r#"
struct Struct;
trait Foo {
fn foo(&self) -> $0(usize, bool);
}
impl Foo for Struct {
fn foo(&self) -> (usize, bool) {
(0, true)
}
}
fn main() {
let s = Struct {};
let (foo, bar) = s.foo();
let (foo, bar) = Struct::foo(&s);
println!("{foo} {bar}");
}
"#,
r#"
struct Struct;
struct FooResult(usize, bool);
trait Foo {
fn foo(&self) -> FooResult;
}
impl Foo for Struct {
fn foo(&self) -> FooResult {
FooResult(0, true)
}
}
fn main() {
let s = Struct {};
let FooResult(foo, bar) = s.foo();
let FooResult(foo, bar) = Struct::foo(&s);
println!("{foo} {bar}");
}
"#,
)
}
#[test]
fn body_wraps_nested() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn foo() -> $0(u8, usize, u32) {
if true {
match 3 {
0 => (1, 2, 3),
_ => return (4, 5, 6),
}
} else {
(2, 1, 3)
}
}
"#,
r#"
struct FooResult(u8, usize, u32);
fn foo() -> FooResult {
if true {
match 3 {
0 => FooResult(1, 2, 3),
_ => return FooResult(4, 5, 6),
}
} else {
FooResult(2, 1, 3)
}
}
"#,
)
}
#[test]
fn body_wraps_break_and_return() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn foo(mut i: isize) -> (usize, $0u32, u8) {
if i < 0 {
return (0, 0, 0);
}
loop {
if i == 2 {
println!("foo");
break (1, 2, 3);
}
i += 1;
}
}
"#,
r#"
struct FooResult(usize, u32, u8);
fn foo(mut i: isize) -> FooResult {
if i < 0 {
return FooResult(0, 0, 0);
}
loop {
if i == 2 {
println!("foo");
break FooResult(1, 2, 3);
}
i += 1;
}
}
"#,
)
}
#[test]
fn body_doesnt_wrap_identifier() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn foo() -> $0(u8, usize, u32) {
let tuple = (1, 2, 3);
tuple
}
"#,
r#"
struct FooResult(u8, usize, u32);
fn foo() -> FooResult {
let tuple = (1, 2, 3);
tuple
}
"#,
)
}
#[test]
fn body_doesnt_wrap_other_exprs() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar(num: usize) -> (u8, usize, u32) {
(1, num, 3)
}
fn foo() -> $0(u8, usize, u32) {
bar(2)
}
"#,
r#"
fn bar(num: usize) -> (u8, usize, u32) {
(1, num, 3)
}
struct FooResult(u8, usize, u32);
fn foo() -> FooResult {
bar(2)
}
"#,
)
}
#[test]
fn cross_file_and_module() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
//- /main.rs
mod foo;
fn main() {
use foo::bar;
let (bar, baz) = bar::bar();
println!("{}", bar == baz);
}
//- /foo.rs
pub mod bar {
pub fn bar() -> $0(usize, usize) {
(1, 3)
}
}
"#,
r#"
//- /main.rs
use crate::foo::bar::BarResult;
mod foo;
fn main() {
use foo::bar;
let BarResult(bar, baz) = bar::bar();
println!("{}", bar == baz);
}
//- /foo.rs
pub mod bar {
pub struct BarResult(pub usize, pub usize);
pub fn bar() -> BarResult {
BarResult(1, 3)
}
}
"#,
)
}
#[test]
fn does_not_replace_nested_usage() {
check_assist(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(usize, bool) {
(42, true)
}
fn main() {
let ((bar1, bar2), foo) = (bar(), 3);
println!("{bar1} {bar2} {foo}");
}
"#,
r#"
struct BarResult(usize, bool);
fn bar() -> BarResult {
BarResult(42, true)
}
fn main() {
let ((bar1, bar2), foo) = (bar(), 3);
println!("{bar1} {bar2} {foo}");
}
"#,
)
}
#[test]
fn function_with_non_tuple_return_type() {
check_assist_not_applicable(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0usize {
0
}
"#,
)
}
#[test]
fn function_with_impl_type() {
check_assist_not_applicable(
convert_tuple_return_type_to_struct,
r#"
fn bar() -> $0(impl Clone, usize) {
("bar", 0)
}
"#,
)
}
}

View file

@ -33,9 +33,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
// Only allow comments which are alone on their line
if let Some(prev) = comment.syntax().prev_token() {
if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
return None;
}
Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
}
let indentation = IndentLevel::from_token(comment.syntax()).to_string();
@ -50,7 +48,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
(
TextRange::new(
comments[0].syntax().text_range().start(),
comments.last().unwrap().syntax().text_range().end(),
comments.last()?.syntax().text_range().end(),
),
Either::Right(comments),
)
@ -71,9 +69,11 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) ->
.map(|l| l.strip_prefix(&indentation).unwrap_or(l))
.join("\n")
}
Either::Right(comments) => {
comments.into_iter().map(|c| line_comment_text(IndentLevel(0), c)).join("\n")
}
Either::Right(comments) => comments
.into_iter()
.map(|cm| line_comment_text(IndentLevel(0), cm))
.collect::<Vec<_>>()
.join("\n"),
};
let hashes = "#".repeat(required_hashes(&text));

View file

@ -29,22 +29,31 @@ use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
// }
// ```
pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
if ctx.has_empty_selection() {
return None;
}
let node = match ctx.covering_element() {
NodeOrToken::Node(it) => it,
NodeOrToken::Token(it) if it.kind() == COMMENT => {
cov_mark::hit!(extract_var_in_comment_is_not_applicable);
let node = if ctx.has_empty_selection() {
if let Some(expr_stmt) = ctx.find_node_at_offset::<ast::ExprStmt>() {
expr_stmt.syntax().clone()
} else if let Some(expr) = ctx.find_node_at_offset::<ast::Expr>() {
expr.syntax().ancestors().find_map(valid_target_expr)?.syntax().clone()
} else {
return None;
}
NodeOrToken::Token(it) => it.parent()?,
} else {
match ctx.covering_element() {
NodeOrToken::Node(it) => it,
NodeOrToken::Token(it) if it.kind() == COMMENT => {
cov_mark::hit!(extract_var_in_comment_is_not_applicable);
return None;
}
NodeOrToken::Token(it) => it.parent()?,
}
};
let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?;
let range = node.text_range();
let to_extract = node
.descendants()
.take_while(|it| ctx.selection_trimmed().contains_range(it.text_range()))
.take_while(|it| range.contains_range(it.text_range()))
.find_map(valid_target_expr)?;
let ty = ctx.sema.type_of_expr(&to_extract).map(TypeInfo::adjusted);
@ -235,6 +244,138 @@ mod tests {
use super::*;
#[test]
fn test_extract_var_simple_without_select() {
check_assist(
extract_variable,
r#"
fn main() -> i32 {
if true {
1
} else {
2
}$0
}
"#,
r#"
fn main() -> i32 {
let $0var_name = if true {
1
} else {
2
};
var_name
}
"#,
);
check_assist(
extract_variable,
r#"
fn foo() -> i32 { 1 }
fn main() {
foo();$0
}
"#,
r#"
fn foo() -> i32 { 1 }
fn main() {
let $0foo = foo();
}
"#,
);
check_assist(
extract_variable,
r#"
fn main() {
let a = Some(2);
a.is_some();$0
}
"#,
r#"
fn main() {
let a = Some(2);
let $0is_some = a.is_some();
}
"#,
);
check_assist(
extract_variable,
r#"
fn main() {
"hello"$0;
}
"#,
r#"
fn main() {
let $0var_name = "hello";
}
"#,
);
check_assist(
extract_variable,
r#"
fn main() {
1 + 2$0;
}
"#,
r#"
fn main() {
let $0var_name = 1 + 2;
}
"#,
);
check_assist(
extract_variable,
r#"
fn main() {
match () {
() if true => 1,
_ => 2,
};$0
}
"#,
r#"
fn main() {
let $0var_name = match () {
() if true => 1,
_ => 2,
};
}
"#,
);
}
#[test]
fn test_extract_var_unit_expr_without_select_not_applicable() {
check_assist_not_applicable(
extract_variable,
r#"
fn foo() {}
fn main() {
foo()$0;
}
"#,
);
check_assist_not_applicable(
extract_variable,
r#"
fn foo() {
let mut i = 3;
if i >= 0 {
i += 1;
} else {
i -= 1;
}$0
}"#,
);
}
#[test]
fn test_extract_var_simple() {
check_assist(

View file

@ -404,7 +404,11 @@ impl FunctionBuilder {
leading_ws,
ret_type: fn_def.ret_type(),
// PANIC: we guarantee we always create a function body with a tail expr
tail_expr: fn_def.body().unwrap().tail_expr().unwrap(),
tail_expr: fn_def
.body()
.expect("generated function should have a body")
.tail_expr()
.expect("function body should have a tail expression"),
should_focus_return_type: self.should_focus_return_type,
fn_def,
trailing_ws,
@ -683,7 +687,7 @@ where
{
// This function should be only called with `Impl`, `Trait`, or `Function`, for which it's
// infallible to get source ast.
let node = ctx.sema.source(def).unwrap().value;
let node = ctx.sema.source(def).expect("definition's source couldn't be found").value;
let generic_params = node.generic_param_list().into_iter().flat_map(|it| it.generic_params());
let where_clauses = node.where_clause().into_iter().flat_map(|it| it.predicates());
(generic_params, where_clauses)

View file

@ -0,0 +1,202 @@
use ide_db::famous_defs::FamousDefs;
use syntax::{
ast::{self, make},
ted, AstNode,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// FIXME: Generate proper `index_mut` method body refer to `index` method body may impossible due to the unpredicable case [#15581].
// Here just leave the `index_mut` method body be same as `index` method body, user can modify it manually to meet their need.
// Assist: generate_mut_trait_impl
//
// Adds a IndexMut impl from the `Index` trait.
//
// ```
// # //- minicore: index
// pub enum Axis { X = 0, Y = 1, Z = 2 }
//
// impl<T> core::ops::Index$0<Axis> for [T; 3] {
// type Output = T;
//
// fn index(&self, index: Axis) -> &Self::Output {
// &self[index as usize]
// }
// }
// ```
// ->
// ```
// pub enum Axis { X = 0, Y = 1, Z = 2 }
//
// $0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
// fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
// &self[index as usize]
// }
// }
//
// impl<T> core::ops::Index<Axis> for [T; 3] {
// type Output = T;
//
// fn index(&self, index: Axis) -> &Self::Output {
// &self[index as usize]
// }
// }
// ```
pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let impl_def = ctx.find_node_at_offset::<ast::Impl>()?.clone_for_update();
let trait_ = impl_def.trait_()?;
if let ast::Type::PathType(trait_path) = trait_.clone() {
let trait_type = ctx.sema.resolve_trait(&trait_path.path()?)?;
let scope = ctx.sema.scope(trait_path.syntax())?;
if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_convert_Index()? {
return None;
}
}
// Index -> IndexMut
let index_trait = impl_def
.syntax()
.descendants()
.filter_map(ast::NameRef::cast)
.find(|it| it.text() == "Index")?;
ted::replace(
index_trait.syntax(),
make::path_segment(make::name_ref("IndexMut")).clone_for_update().syntax(),
);
// index -> index_mut
let trait_method_name = impl_def
.syntax()
.descendants()
.filter_map(ast::Name::cast)
.find(|it| it.text() == "index")?;
ted::replace(trait_method_name.syntax(), make::name("index_mut").clone_for_update().syntax());
let type_alias = impl_def.syntax().descendants().find_map(ast::TypeAlias::cast)?;
ted::remove(type_alias.syntax());
// &self -> &mut self
let mut_self_param = make::mut_self_param();
let self_param: ast::SelfParam =
impl_def.syntax().descendants().find_map(ast::SelfParam::cast)?;
ted::replace(self_param.syntax(), mut_self_param.clone_for_update().syntax());
// &Self::Output -> &mut Self::Output
let ret_type = impl_def.syntax().descendants().find_map(ast::RetType::cast)?;
ted::replace(
ret_type.syntax(),
make::ret_type(make::ty("&mut Self::Output")).clone_for_update().syntax(),
);
let fn_ = impl_def.assoc_item_list()?.assoc_items().find_map(|it| match it {
ast::AssocItem::Fn(f) => Some(f),
_ => None,
})?;
let assoc_list = make::assoc_item_list().clone_for_update();
assoc_list.add_item(syntax::ast::AssocItem::Fn(fn_));
ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax());
let target = impl_def.syntax().text_range();
acc.add(
AssistId("generate_mut_trait_impl", AssistKind::Generate),
"Generate `IndexMut` impl from this `Index` trait",
target,
|edit| {
edit.insert(target.start(), format!("$0{}\n\n", impl_def.to_string()));
},
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn test_generate_mut_trait_impl() {
check_assist(
generate_mut_trait_impl,
r#"
//- minicore: index
pub enum Axis { X = 0, Y = 1, Z = 2 }
impl<T> core::ops::Index$0<Axis> for [T; 3] {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
&self[index as usize]
}
}
"#,
r#"
pub enum Axis { X = 0, Y = 1, Z = 2 }
$0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
&self[index as usize]
}
}
impl<T> core::ops::Index<Axis> for [T; 3] {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
&self[index as usize]
}
}
"#,
);
check_assist(
generate_mut_trait_impl,
r#"
//- minicore: index
pub enum Axis { X = 0, Y = 1, Z = 2 }
impl<T> core::ops::Index$0<Axis> for [T; 3] where T: Copy {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
let var_name = &self[index as usize];
var_name
}
}
"#,
r#"
pub enum Axis { X = 0, Y = 1, Z = 2 }
$0impl<T> core::ops::IndexMut<Axis> for [T; 3] where T: Copy {
fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
let var_name = &self[index as usize];
var_name
}
}
impl<T> core::ops::Index<Axis> for [T; 3] where T: Copy {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
let var_name = &self[index as usize];
var_name
}
}
"#,
);
}
#[test]
fn test_generate_mut_trait_impl_not_applicable() {
check_assist_not_applicable(
generate_mut_trait_impl,
r#"
pub trait Index<Idx: ?Sized> {}
impl<T> Index$0<i32> for [T; 3] {}
"#,
);
}
}

View file

@ -224,7 +224,6 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
syntax.text_range(),
|builder| {
let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
builder.replace_ast(
match call_info.node {
ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it),
@ -363,16 +362,22 @@ fn inline(
.collect();
if function.self_param(sema.db).is_some() {
let this = || make::name_ref("this").syntax().clone_for_update().first_token().unwrap();
let this = || {
make::name_ref("this")
.syntax()
.clone_for_update()
.first_token()
.expect("NameRef should have had a token.")
};
if let Some(self_local) = params[0].2.as_local(sema.db) {
usages_for_locals(self_local)
.filter_map(|FileReference { name, range, .. }| match name {
ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
_ => None,
})
.for_each(|it| {
ted::replace(it, &this());
})
.for_each(|usage| {
ted::replace(usage, &this());
});
}
}
@ -470,7 +475,9 @@ fn inline(
}
} else if let Some(stmt_list) = body.stmt_list() {
ted::insert_all(
ted::Position::after(stmt_list.l_curly_token().unwrap()),
ted::Position::after(
stmt_list.l_curly_token().expect("L_CURLY for StatementList is missing."),
),
let_stmts.into_iter().map(|stmt| stmt.syntax().clone().into()).collect(),
);
}
@ -481,8 +488,12 @@ fn inline(
};
body.reindent_to(original_indentation);
let no_stmts = body.statements().next().is_none();
match body.tail_expr() {
Some(expr) if !is_async_fn && body.statements().next().is_none() => expr,
Some(expr) if matches!(expr, ast::Expr::ClosureExpr(_)) && no_stmts => {
make::expr_paren(expr).clone_for_update()
}
Some(expr) if !is_async_fn && no_stmts => expr,
_ => match node
.syntax()
.parent()
@ -1471,6 +1482,31 @@ fn main() {
}
});
}
"#,
);
}
#[test]
fn inline_call_closure_body() {
check_assist(
inline_call,
r#"
fn f() -> impl Fn() -> i32 {
|| 2
}
fn main() {
let _ = $0f()();
}
"#,
r#"
fn f() -> impl Fn() -> i32 {
|| 2
}
fn main() {
let _ = (|| 2)();
}
"#,
);
}

View file

@ -96,8 +96,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
);
let parent = matches!(
usage_parent,
ast::Expr::CallExpr(_)
| ast::Expr::TupleExpr(_)
ast::Expr::TupleExpr(_)
| ast::Expr::ArrayExpr(_)
| ast::Expr::ParenExpr(_)
| ast::Expr::ForExpr(_)
@ -949,6 +948,24 @@ fn f() {
let S$0 = S;
S;
}
"#,
);
}
#[test]
fn test_inline_closure() {
check_assist(
inline_local_variable,
r#"
fn main() {
let $0f = || 2;
let _ = f();
}
"#,
r#"
fn main() {
let _ = (|| 2)();
}
"#,
);
}

View file

@ -52,9 +52,13 @@ pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>)
== FamousDefs(sema, scope.krate()).core_convert_Into()?
{
let type_call = sema.type_of_expr(&method_call.clone().into())?;
let type_call_disp =
type_call.adjusted().display_source_code(db, scope.module().into(), true).ok()?;
let adjusted_tc = type_call.adjusted();
if adjusted_tc.contains_unknown() {
return None;
}
let sc = adjusted_tc.display_source_code(db, scope.module().into(), true).ok()?;
acc.add(
AssistId("into_to_qualified_from", AssistKind::Generate),
"Convert `into` to fully qualified `from`",
@ -62,7 +66,11 @@ pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>)
|edit| {
edit.replace(
method_call.syntax().text_range(),
format!("{}::from({})", type_call_disp, receiver),
if sc.chars().all(|c| c.is_alphanumeric() || c == ':') {
format!("{}::from({})", sc, receiver)
} else {
format!("<{}>::from({})", sc, receiver)
},
);
},
);
@ -199,6 +207,66 @@ mod C {
fn main() -> () {
let a: A = A;
let b: C::B = C::B::from(a);
}"#,
)
}
#[test]
fn preceding_type_qualifier() {
check_assist(
into_to_qualified_from,
r#"
//- minicore: from
impl From<(i32,i32)> for [i32;2] {
fn from(value: (i32,i32)) -> Self {
[value.0, value.1]
}
}
fn tuple_to_array() -> [i32; 2] {
(0,1).in$0to()
}"#,
r#"
impl From<(i32,i32)> for [i32;2] {
fn from(value: (i32,i32)) -> Self {
[value.0, value.1]
}
}
fn tuple_to_array() -> [i32; 2] {
<[i32; 2]>::from((0,1))
}"#,
)
}
#[test]
fn type_with_gens() {
check_assist(
into_to_qualified_from,
r#"
//- minicore: from
struct StructA<Gen>(Gen);
impl From<i32> for StructA<i32> {
fn from(value: i32) -> Self {
StructA(value + 1)
}
}
fn main() -> () {
let a: StructA<i32> = 3.in$0to();
}"#,
r#"
struct StructA<Gen>(Gen);
impl From<i32> for StructA<i32> {
fn from(value: i32) -> Self {
StructA(value + 1)
}
}
fn main() -> () {
let a: StructA<i32> = <StructA<i32>>::from(3);
}"#,
)
}

View file

@ -0,0 +1,172 @@
use syntax::ast::{self, AstNode};
use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_is_some_with_if_let_some
//
// Replace `if x.is_some()` with `if let Some(_tmp) = x` or `if x.is_ok()` with `if let Ok(_tmp) = x`.
//
// ```
// fn main() {
// let x = Some(1);
// if x.is_som$0e() {}
// }
// ```
// ->
// ```
// fn main() {
// let x = Some(1);
// if let Some(${0:x}) = x {}
// }
// ```
pub(crate) fn replace_is_method_with_if_let_method(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
let if_expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
let cond = if_expr.condition()?;
let call_expr = match cond {
ast::Expr::MethodCallExpr(call) => call,
_ => return None,
};
let name_ref = call_expr.name_ref()?;
match name_ref.text().as_str() {
"is_some" | "is_ok" => {
let receiver = call_expr.receiver()?;
let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() {
path_expr.path()?.to_string()
} else {
suggest_name::for_variable(&receiver, &ctx.sema)
};
let target = call_expr.syntax().text_range();
let (assist_id, message, text) = if name_ref.text() == "is_some" {
("replace_is_some_with_if_let_some", "Replace `is_some` with `if let Some`", "Some")
} else {
("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `if let Ok`", "Ok")
};
acc.add(AssistId(assist_id, AssistKind::RefactorRewrite), message, target, |edit| {
let var_name = format!("${{0:{}}}", var_name);
let replacement = format!("let {}({}) = {}", text, var_name, receiver);
edit.replace(target, replacement);
})
}
_ => return None,
}
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::replace_is_method_with_if_let_method;
#[test]
fn replace_is_some_with_if_let_some_works() {
check_assist(
replace_is_method_with_if_let_method,
r#"
fn main() {
let x = Some(1);
if x.is_som$0e() {}
}
"#,
r#"
fn main() {
let x = Some(1);
if let Some(${0:x}) = x {}
}
"#,
);
check_assist(
replace_is_method_with_if_let_method,
r#"
fn test() -> Option<i32> {
Some(1)
}
fn main() {
if test().is_som$0e() {}
}
"#,
r#"
fn test() -> Option<i32> {
Some(1)
}
fn main() {
if let Some(${0:test}) = test() {}
}
"#,
);
}
#[test]
fn replace_is_some_with_if_let_some_not_applicable() {
check_assist_not_applicable(
replace_is_method_with_if_let_method,
r#"
fn main() {
let x = Some(1);
if x.is_non$0e() {}
}
"#,
);
}
#[test]
fn replace_is_ok_with_if_let_ok_works() {
check_assist(
replace_is_method_with_if_let_method,
r#"
fn main() {
let x = Ok(1);
if x.is_o$0k() {}
}
"#,
r#"
fn main() {
let x = Ok(1);
if let Ok(${0:x}) = x {}
}
"#,
);
check_assist(
replace_is_method_with_if_let_method,
r#"
fn test() -> Result<i32> {
Ok(1)
}
fn main() {
if test().is_o$0k() {}
}
"#,
r#"
fn test() -> Result<i32> {
Ok(1)
}
fn main() {
if let Ok(${0:test}) = test() {}
}
"#,
);
}
#[test]
fn replace_is_ok_with_if_let_ok_not_applicable() {
check_assist_not_applicable(
replace_is_method_with_if_let_method,
r#"
fn main() {
let x = Ok(1);
if x.is_e$0rr() {}
}
"#,
);
}
}

View file

@ -36,29 +36,25 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
let old_parent_range = use_.syntax().parent()?.text_range();
let new_parent = use_.syntax().parent()?;
// If possible, explain what is going to be done.
let label = match tree.path().and_then(|path| path.first_segment()) {
Some(name) => format!("Unmerge use of `{name}`"),
None => "Unmerge use".into(),
};
let target = tree.syntax().text_range();
acc.add(
AssistId("unmerge_use", AssistKind::RefactorRewrite),
"Unmerge use",
target,
|builder| {
let new_use = make::use_(
use_.visibility(),
make::use_tree(
path,
tree.use_tree_list(),
tree.rename(),
tree.star_token().is_some(),
),
)
.clone_for_update();
acc.add(AssistId("unmerge_use", AssistKind::RefactorRewrite), label, target, |builder| {
let new_use = make::use_(
use_.visibility(),
make::use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
)
.clone_for_update();
tree.remove();
ted::insert(Position::after(use_.syntax()), new_use.syntax());
tree.remove();
ted::insert(Position::after(use_.syntax()), new_use.syntax());
builder.replace(old_parent_range, new_parent.to_string());
},
)
builder.replace(old_parent_range, new_parent.to_string());
})
}
fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {

View file

@ -1,3 +1,4 @@
use ide_db::imports::insert_use::ImportScope;
use syntax::{
ast::{self, make, AstNode, HasArgList},
TextRange,
@ -17,6 +18,8 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
// ```
// ->
// ```
// use std::ops::Add;
//
// fn main() {
// 1.add(2);
// }
@ -38,7 +41,7 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>)
let first_arg = args_iter.next()?;
let second_arg = args_iter.next();
_ = path.qualifier()?;
let qualifier = path.qualifier()?;
let method_name = path.segment()?.name_ref()?;
let res = ctx.sema.resolve_path(&path)?;
@ -76,10 +79,51 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>)
edit.insert(close, ")");
}
edit.replace(replace_comma, format!(".{method_name}("));
add_import(qualifier, ctx, edit);
},
)
}
fn add_import(
qualifier: ast::Path,
ctx: &AssistContext<'_>,
edit: &mut ide_db::source_change::SourceChangeBuilder,
) {
if let Some(path_segment) = qualifier.segment() {
// for `<i32 as std::ops::Add>`
let path_type = path_segment.syntax().children().filter_map(ast::PathType::cast).last();
let import = match path_type {
Some(it) => {
if let Some(path) = it.path() {
path
} else {
return;
}
}
None => qualifier,
};
// in case for `<_>`
if import.coloncolon_token().is_none() {
return;
}
let scope = ide_db::imports::insert_use::ImportScope::find_insert_use_container(
import.syntax(),
&ctx.sema,
);
if let Some(scope) = scope {
let scope = match scope {
ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
};
ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
}
}
}
fn needs_parens_as_receiver(expr: &ast::Expr) -> bool {
// Make `(expr).dummy()`
let dummy_call = make::expr_method_call(
@ -127,6 +171,8 @@ fn f() { S.f(S); }"#,
//- minicore: add
fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#,
r#"
use core::ops::Add;
fn f() { 2.add(2); }"#,
);
@ -136,6 +182,8 @@ fn f() { 2.add(2); }"#,
//- minicore: add
fn f() { core::ops::Add::$0add(2, 2); }"#,
r#"
use core::ops::Add;
fn f() { 2.add(2); }"#,
);
@ -179,6 +227,8 @@ impl core::ops::Deref for S {
}
fn f() { core::ops::Deref::$0deref(&S); }"#,
r#"
use core::ops::Deref;
struct S;
impl core::ops::Deref for S {
type Target = S;

View file

@ -123,10 +123,8 @@ fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
}
}
Expr::ReturnExpr(ret_expr) => {
if let Some(ret_expr_arg) = &ret_expr.expr() {
for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
}
Expr::ReturnExpr(_) => {
// all return expressions have already been handled by the walk loop
}
e => acc.push(e.clone()),
}
@ -800,6 +798,24 @@ fn foo() -> i32 {
);
}
#[test]
fn wrap_return_in_tail_position() {
check_assist(
unwrap_result_return_type,
r#"
//- minicore: result
fn foo(num: i32) -> $0Result<i32, String> {
return Ok(num)
}
"#,
r#"
fn foo(num: i32) -> i32 {
return num
}
"#,
);
}
#[test]
fn unwrap_result_return_type_simple_with_closure() {
check_assist(

View file

@ -98,10 +98,8 @@ fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
}
}
Expr::ReturnExpr(ret_expr) => {
if let Some(ret_expr_arg) = &ret_expr.expr() {
for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
}
Expr::ReturnExpr(_) => {
// all return expressions have already been handled by the walk loop
}
e => acc.push(e.clone()),
}
@ -732,6 +730,24 @@ fn foo() -> Result<i32, ${0:_}> {
);
}
#[test]
fn wrap_return_in_tail_position() {
check_assist(
wrap_return_type_in_result,
r#"
//- minicore: result
fn foo(num: i32) -> $0i32 {
return num
}
"#,
r#"
fn foo(num: i32) -> Result<i32, ${0:_}> {
return Ok(num)
}
"#,
);
}
#[test]
fn wrap_return_type_in_result_simple_with_closure() {
check_assist(

View file

@ -115,6 +115,7 @@ mod handlers {
mod apply_demorgan;
mod auto_import;
mod bind_unused_param;
mod bool_to_enum;
mod change_visibility;
mod convert_bool_then;
mod convert_comment_block;
@ -124,6 +125,7 @@ mod handlers {
mod convert_let_else_to_match;
mod convert_match_to_let_else;
mod convert_nested_function_to_closure;
mod convert_tuple_return_type_to_struct;
mod convert_tuple_struct_to_named_struct;
mod convert_named_struct_to_tuple_struct;
mod convert_to_guarded_return;
@ -158,6 +160,7 @@ mod handlers {
mod generate_getter_or_setter;
mod generate_impl;
mod generate_is_empty_from_len;
mod generate_mut_trait_impl;
mod generate_new;
mod generate_delegate_methods;
mod generate_trait_from_impl;
@ -193,6 +196,7 @@ mod handlers {
mod replace_try_expr_with_match;
mod replace_derive_with_manual_impl;
mod replace_if_let_with_match;
mod replace_is_method_with_if_let_method;
mod replace_method_eager_lazy;
mod replace_arith_op;
mod introduce_named_generic;
@ -225,8 +229,10 @@ mod handlers {
add_return_type::add_return_type,
add_turbo_fish::add_turbo_fish,
apply_demorgan::apply_demorgan,
apply_demorgan::apply_demorgan_iterator,
auto_import::auto_import,
bind_unused_param::bind_unused_param,
bool_to_enum::bool_to_enum,
change_visibility::change_visibility,
convert_bool_then::convert_bool_then_to_if,
convert_bool_then::convert_if_to_bool_then,
@ -237,6 +243,7 @@ mod handlers {
convert_iter_for_each_to_for::convert_for_loop_with_for_each,
convert_let_else_to_match::convert_let_else_to_match,
convert_match_to_let_else::convert_match_to_let_else,
convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct,
convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
convert_nested_function_to_closure::convert_nested_function_to_closure,
convert_to_guarded_return::convert_to_guarded_return,
@ -268,6 +275,7 @@ mod handlers {
generate_function::generate_function,
generate_impl::generate_impl,
generate_impl::generate_trait_impl,
generate_mut_trait_impl::generate_mut_trait_impl,
generate_is_empty_from_len::generate_is_empty_from_len,
generate_new::generate_new,
generate_trait_from_impl::generate_trait_from_impl,
@ -308,6 +316,7 @@ mod handlers {
replace_derive_with_manual_impl::replace_derive_with_manual_impl,
replace_if_let_with_match::replace_if_let_with_match,
replace_if_let_with_match::replace_match_with_if_let,
replace_is_method_with_if_let_method::replace_is_method_with_if_let_method,
replace_let_with_if_let::replace_let_with_if_let,
replace_method_eager_lazy::replace_with_eager_method,
replace_method_eager_lazy::replace_with_lazy_method,

View file

@ -244,6 +244,30 @@ fn main() {
)
}
#[test]
fn doctest_apply_demorgan_iterator() {
check_doc_test(
"apply_demorgan_iterator",
r#####"
//- minicore: iterator
fn main() {
let arr = [1, 2, 3];
if !arr.into_iter().$0any(|num| num == 4) {
println!("foo");
}
}
"#####,
r#####"
fn main() {
let arr = [1, 2, 3];
if arr.into_iter().all(|num| num != 4) {
println!("foo");
}
}
"#####,
)
}
#[test]
fn doctest_auto_import() {
check_doc_test(
@ -280,6 +304,34 @@ fn some_function(x: i32) {
)
}
#[test]
fn doctest_bool_to_enum() {
check_doc_test(
"bool_to_enum",
r#####"
fn main() {
let $0bool = true;
if bool {
println!("foo");
}
}
"#####,
r#####"
#[derive(PartialEq, Eq)]
enum Bool { True, False }
fn main() {
let bool = Bool::True;
if bool == Bool::True {
println!("foo");
}
}
"#####,
)
}
#[test]
fn doctest_change_visibility() {
check_doc_test(
@ -558,6 +610,33 @@ fn main() {
)
}
#[test]
fn doctest_convert_tuple_return_type_to_struct() {
check_doc_test(
"convert_tuple_return_type_to_struct",
r#####"
fn bar() {
let (a, b, c) = foo();
}
fn foo() -> ($0u32, u32, u32) {
(1, 2, 3)
}
"#####,
r#####"
fn bar() {
let FooResult(a, b, c) = foo();
}
struct FooResult(u32, u32, u32);
fn foo() -> FooResult {
FooResult(1, 2, 3)
}
"#####,
)
}
#[test]
fn doctest_convert_tuple_struct_to_named_struct() {
check_doc_test(
@ -1459,6 +1538,42 @@ impl MyStruct {
)
}
#[test]
fn doctest_generate_mut_trait_impl() {
check_doc_test(
"generate_mut_trait_impl",
r#####"
//- minicore: index
pub enum Axis { X = 0, Y = 1, Z = 2 }
impl<T> core::ops::Index$0<Axis> for [T; 3] {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
&self[index as usize]
}
}
"#####,
r#####"
pub enum Axis { X = 0, Y = 1, Z = 2 }
$0impl<T> core::ops::IndexMut<Axis> for [T; 3] {
fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
&self[index as usize]
}
}
impl<T> core::ops::Index<Axis> for [T; 3] {
type Output = T;
fn index(&self, index: Axis) -> &Self::Output {
&self[index as usize]
}
}
"#####,
)
}
#[test]
fn doctest_generate_new() {
check_doc_test(
@ -2479,6 +2594,25 @@ fn handle(action: Action) {
)
}
#[test]
fn doctest_replace_is_some_with_if_let_some() {
check_doc_test(
"replace_is_some_with_if_let_some",
r#####"
fn main() {
let x = Some(1);
if x.is_som$0e() {}
}
"#####,
r#####"
fn main() {
let x = Some(1);
if let Some(${0:x}) = x {}
}
"#####,
)
}
#[test]
fn doctest_replace_let_with_if_let() {
check_doc_test(
@ -2850,6 +2984,8 @@ fn main() {
mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
"#####,
r#####"
use std::ops::Add;
fn main() {
1.add(2);
}

View file

@ -13,10 +13,9 @@ use crate::{
TypeLocation,
},
render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
Completions,
};
use super::Completions;
// Feature: Completion With Autoimport
//
// When completing names in the current scope, proposes additional imports from other modules or crates,
@ -377,9 +376,12 @@ fn import_assets_for_path(
&ctx.sema,
ctx.token.parent()?,
)?;
if fuzzy_name_length < 3 {
cov_mark::hit!(flyimport_exact_on_short_path);
assets_for_path.path_fuzzy_name_to_exact(false);
if fuzzy_name_length == 0 {
// nothing matches the empty string exactly, but we still compute assoc items in this case
assets_for_path.path_fuzzy_name_to_exact();
} else if fuzzy_name_length < 3 {
cov_mark::hit!(flyimport_prefix_on_short_path);
assets_for_path.path_fuzzy_name_to_prefix();
}
Some(assets_for_path)
}

View file

@ -14,6 +14,7 @@ pub struct CompletionConfig {
pub enable_imports_on_the_fly: bool,
pub enable_self_on_the_fly: bool,
pub enable_private_editable: bool,
pub full_function_signatures: bool,
pub callable: Option<CallableSnippets>,
pub snippet_cap: Option<SnippetCap>,
pub insert_use: InsertUseConfig,

View file

@ -89,7 +89,7 @@ impl fmt::Debug for CompletionItem {
let mut s = f.debug_struct("CompletionItem");
s.field("label", &self.label).field("source_range", &self.source_range);
if self.text_edit.len() == 1 {
let atom = &self.text_edit.iter().next().unwrap();
let atom = self.text_edit.iter().next().unwrap();
s.field("delete", &atom.delete);
s.field("insert", &atom.insert);
} else {

View file

@ -169,6 +169,28 @@ pub fn completions(
return Some(completions.into());
}
// when the user types a bare `_` (that is it does not belong to an identifier)
// the user might just wanted to type a `_` for type inference or pattern discarding
// so try to suppress completions in those cases
if trigger_character == Some('_') && ctx.original_token.kind() == syntax::SyntaxKind::UNDERSCORE
{
if let CompletionAnalysis::NameRef(NameRefContext {
kind:
NameRefKind::Path(
path_ctx @ PathCompletionCtx {
kind: PathKind::Type { .. } | PathKind::Pat { .. },
..
},
),
..
}) = analysis
{
if path_ctx.is_trivial_path() {
return None;
}
}
}
{
let acc = &mut completions;

View file

@ -98,9 +98,14 @@ fn render(
_ => (),
}
let detail = if ctx.completion.config.full_function_signatures {
detail_full(db, func)
} else {
detail(db, func)
};
item.set_documentation(ctx.docs(func))
.set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
.detail(detail(db, func))
.detail(detail)
.lookup_by(name.unescaped().to_smol_str());
match ctx.completion.config.snippet_cap {
@ -263,6 +268,21 @@ fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
detail
}
fn detail_full(db: &dyn HirDatabase, func: hir::Function) -> String {
let signature = format!("{}", func.display(db));
let mut detail = String::with_capacity(signature.len());
for segment in signature.split_whitespace() {
if !detail.is_empty() {
detail.push(' ');
}
detail.push_str(segment);
}
detail
}
fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
if let Some(self_param) = func.self_param(db) {
let assoc_fn_params = func.assoc_fn_params(db);

View file

@ -64,6 +64,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
enable_imports_on_the_fly: true,
enable_self_on_the_fly: true,
enable_private_editable: false,
full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true),
prefer_no_std: false,

View file

@ -116,19 +116,47 @@ fn main() {
}
#[test]
fn short_paths_are_ignored() {
cov_mark::check!(flyimport_exact_on_short_path);
fn short_paths_are_prefix_matched() {
cov_mark::check!(flyimport_prefix_on_short_path);
check(
r#"
//- /lib.rs crate:dep
pub struct Bar;
pub struct Barc;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
pub mod some_module {
pub struct Bar;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
}
//- /main.rs crate:main deps:dep
fn main() {
Rc$0
}
"#,
expect![[r#"
st Rc (use dep::Rc)
st Rcar (use dep::Rcar)
st Rc (use dep::some_module::Rc)
st Rcar (use dep::some_module::Rcar)
"#]],
);
check(
r#"
//- /lib.rs crate:dep
pub struct Barc;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
pub mod some_module {
pub struct Bar;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
}
//- /main.rs crate:main deps:dep
@ -137,8 +165,36 @@ fn main() {
}
"#,
expect![[r#"
ct RC (use dep::RC)
st Rc (use dep::Rc)
st Rcar (use dep::Rcar)
ct RC (use dep::some_module::RC)
st Rc (use dep::some_module::Rc)
st Rcar (use dep::some_module::Rcar)
"#]],
);
check(
r#"
//- /lib.rs crate:dep
pub struct Barc;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
pub mod some_module {
pub struct Bar;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
}
//- /main.rs crate:main deps:dep
fn main() {
RC$0
}
"#,
expect![[r#"
ct RC (use dep::RC)
ct RC (use dep::some_module::RC)
"#]],
);
}
@ -841,8 +897,8 @@ fn main() {
TES$0
}"#,
expect![[r#"
ct TEST_CONST (use foo::TEST_CONST)
"#]],
ct TEST_CONST (use foo::TEST_CONST)
"#]],
);
check(
@ -858,9 +914,9 @@ fn main() {
tes$0
}"#,
expect![[r#"
ct TEST_CONST (use foo::TEST_CONST)
fn test_function() (use foo::test_function) fn() -> i32
"#]],
ct TEST_CONST (use foo::TEST_CONST)
fn test_function() (use foo::test_function) fn() -> i32
"#]],
);
check(
@ -873,9 +929,9 @@ mod foo {
}
fn main() {
Te$0
Tes$0
}"#,
expect![[]],
expect![""],
);
}

View file

@ -2,10 +2,15 @@
use expect_test::{expect, Expect};
use crate::tests::{
check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character,
use crate::{
tests::{
check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character,
},
CompletionItemKind,
};
use super::{do_completion_with_config, TEST_CONFIG};
fn check_no_kw(ra_fixture: &str, expect: Expect) {
let actual = completion_list_no_kw(ra_fixture);
expect.assert_eq(&actual)
@ -1303,3 +1308,176 @@ struct Foo<T: PartialOrd
"#,
);
}
fn check_signatures(src: &str, kind: CompletionItemKind, reduced: Expect, full: Expect) {
const FULL_SIGNATURES_CONFIG: crate::CompletionConfig = {
let mut x = TEST_CONFIG;
x.full_function_signatures = true;
x
};
// reduced signature
let completion = do_completion_with_config(TEST_CONFIG, src, kind);
assert!(completion[0].detail.is_some());
reduced.assert_eq(completion[0].detail.as_ref().unwrap());
// full signature
let completion = do_completion_with_config(FULL_SIGNATURES_CONFIG, src, kind);
assert!(completion[0].detail.is_some());
full.assert_eq(completion[0].detail.as_ref().unwrap());
}
#[test]
fn respects_full_function_signatures() {
check_signatures(
r#"
pub fn foo<'x, T>(x: &'x mut T) -> u8 where T: Clone, { 0u8 }
fn main() { fo$0 }
"#,
CompletionItemKind::SymbolKind(ide_db::SymbolKind::Function),
expect!("fn(&mut T) -> u8"),
expect!("pub fn foo<'x, T>(x: &'x mut T) -> u8 where T: Clone,"),
);
check_signatures(
r#"
struct Foo;
struct Bar;
impl Bar {
pub const fn baz(x: Foo) -> ! { loop {} };
}
fn main() { Bar::b$0 }
"#,
CompletionItemKind::SymbolKind(ide_db::SymbolKind::Function),
expect!("const fn(Foo) -> !"),
expect!("pub const fn baz(x: Foo) -> !"),
);
check_signatures(
r#"
struct Foo;
struct Bar;
impl Bar {
pub const fn baz<'foo>(&'foo mut self, x: &'foo Foo) -> ! { loop {} };
}
fn main() {
let mut bar = Bar;
bar.b$0
}
"#,
CompletionItemKind::Method,
expect!("const fn(&'foo mut self, &Foo) -> !"),
expect!("pub const fn baz<'foo>(&'foo mut self, x: &'foo Foo) -> !"),
);
}
#[test]
fn skips_underscore() {
check_with_trigger_character(
r#"
fn foo(_$0) { }
"#,
Some('_'),
expect![[r#""#]],
);
check_with_trigger_character(
r#"
fn foo(_: _$0) { }
"#,
Some('_'),
expect![[r#""#]],
);
check_with_trigger_character(
r#"
fn foo<T>() {
foo::<_$0>();
}
"#,
Some('_'),
expect![[r#""#]],
);
// underscore expressions are fine, they are invalid so the user definitely meant to type an
// underscored name here
check_with_trigger_character(
r#"
fn foo() {
_$0
}
"#,
Some('_'),
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
}
#[test]
fn no_skip_underscore_ident() {
check_with_trigger_character(
r#"
fn foo(a_$0) { }
"#,
Some('_'),
expect![[r#"
kw mut
kw ref
"#]],
);
check_with_trigger_character(
r#"
fn foo(_: a_$0) { }
"#,
Some('_'),
expect![[r#"
bt u32
kw crate::
kw self::
"#]],
);
check_with_trigger_character(
r#"
fn foo<T>() {
foo::<a_$0>();
}
"#,
Some('_'),
expect![[r#"
tp T
bt u32
kw crate::
kw self::
"#]],
);
}

View file

@ -161,8 +161,8 @@ impl IdentClass {
ast::AwaitExpr(await_expr) => OperatorClass::classify_await(sema, &await_expr).map(IdentClass::Operator),
ast::BinExpr(bin_expr) => OperatorClass::classify_bin(sema, &bin_expr).map(IdentClass::Operator),
ast::IndexExpr(index_expr) => OperatorClass::classify_index(sema, &index_expr).map(IdentClass::Operator),
ast::PrefixExpr(prefix_expr) => OperatorClass::classify_prefix(sema,&prefix_expr).map(IdentClass::Operator),
ast::TryExpr(try_expr) => OperatorClass::classify_try(sema,&try_expr).map(IdentClass::Operator),
ast::PrefixExpr(prefix_expr) => OperatorClass::classify_prefix(sema, &prefix_expr).map(IdentClass::Operator),
ast::TryExpr(try_expr) => OperatorClass::classify_try(sema, &try_expr).map(IdentClass::Operator),
_ => None,
}
}

View file

@ -54,6 +54,10 @@ impl FamousDefs<'_, '_> {
self.find_trait("core:convert:Into")
}
pub fn core_convert_Index(&self) -> Option<Trait> {
self.find_trait("core:ops:Index")
}
pub fn core_option_Option(&self) -> Option<Enum> {
self.find_enum("core:option:Option")
}

File diff suppressed because it is too large Load diff

View file

@ -68,22 +68,29 @@ pub struct FirstSegmentUnresolved {
pub enum NameToImport {
/// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
Exact(String, bool),
/// Requires items with names that case-insensitively contain all letters from the string,
/// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
Prefix(String, bool),
/// Requires items with names contain all letters from the string,
/// in the same order, but not necessary adjacent.
Fuzzy(String),
Fuzzy(String, bool),
}
impl NameToImport {
pub fn exact_case_sensitive(s: String) -> NameToImport {
NameToImport::Exact(s, true)
}
}
impl NameToImport {
pub fn fuzzy(s: String) -> NameToImport {
// unless all chars are lowercase, we do a case sensitive search
let case_sensitive = s.chars().any(|c| c.is_uppercase());
NameToImport::Fuzzy(s, case_sensitive)
}
pub fn text(&self) -> &str {
match self {
NameToImport::Exact(text, _) => text.as_str(),
NameToImport::Fuzzy(text) => text.as_str(),
NameToImport::Prefix(text, _)
| NameToImport::Exact(text, _)
| NameToImport::Fuzzy(text, _) => text.as_str(),
}
}
}
@ -165,7 +172,7 @@ impl ImportAssets {
Some(Self {
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
receiver_ty,
assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
}),
module_with_candidate: module_with_method_call,
candidate_node,
@ -228,12 +235,30 @@ impl ImportAssets {
self.search_for(sema, None, prefer_no_std)
}
pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
/// Requires imports to by prefix instead of fuzzily.
pub fn path_fuzzy_name_to_prefix(&mut self) {
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
&mut self.import_candidate
{
let name = match to_import {
NameToImport::Fuzzy(name) => std::mem::take(name),
let (name, case_sensitive) = match to_import {
NameToImport::Fuzzy(name, case_sensitive) => {
(std::mem::take(name), *case_sensitive)
}
_ => return,
};
*to_import = NameToImport::Prefix(name, case_sensitive);
}
}
/// Requires imports to match exactly instead of fuzzily.
pub fn path_fuzzy_name_to_exact(&mut self) {
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
&mut self.import_candidate
{
let (name, case_sensitive) = match to_import {
NameToImport::Fuzzy(name, case_sensitive) => {
(std::mem::take(name), *case_sensitive)
}
_ => return,
};
*to_import = NameToImport::Exact(name, case_sensitive);
@ -623,7 +648,7 @@ impl ImportCandidate {
fuzzy_name: String,
sema: &Semantics<'_, RootDatabase>,
) -> Option<Self> {
path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
}
}

View file

@ -9,7 +9,7 @@ use syntax::{
algo,
ast::{
self, edit_in_place::Removable, make, AstNode, HasAttrs, HasModuleItem, HasVisibility,
PathSegmentKind,
PathSegmentKind, UseTree,
},
ted, Direction, NodeOrToken, SyntaxKind, SyntaxNode,
};
@ -157,6 +157,29 @@ impl ImportScope {
/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
insert_use_with_alias_option(scope, path, cfg, None);
}
pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
let text: &str = "use foo as _";
let parse = syntax::SourceFile::parse(text);
let node = parse
.tree()
.syntax()
.descendants()
.find_map(UseTree::cast)
.expect("Failed to make ast node `Rename`");
let alias = node.rename();
insert_use_with_alias_option(scope, path, cfg, alias);
}
fn insert_use_with_alias_option(
scope: &ImportScope,
path: ast::Path,
cfg: &InsertUseConfig,
alias: Option<ast::Rename>,
) {
let _p = profile::span("insert_use");
let mut mb = match cfg.granularity {
ImportGranularity::Crate => Some(MergeBehavior::Crate),
@ -176,7 +199,8 @@ pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
}
let use_item =
make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
make::use_(None, make::use_tree(path.clone(), None, alias, false)).clone_for_update();
// merge into existing imports if possible
if let Some(mb) = mb {
let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));

View file

@ -993,6 +993,46 @@ use foo::bar::qux;
);
}
#[test]
fn insert_with_renamed_import_simple_use() {
check_with_config(
"use self::foo::Foo",
r#"
use self::foo::Foo as _;
"#,
r#"
use self::foo::Foo;
"#,
&InsertUseConfig {
granularity: ImportGranularity::Crate,
prefix_kind: hir::PrefixKind::BySelf,
enforce_granularity: true,
group: true,
skip_glob_imports: true,
},
);
}
#[test]
fn insert_with_renamed_import_complex_use() {
check_with_config(
"use self::foo::Foo;",
r#"
use self::foo::{self, Foo as _, Bar};
"#,
r#"
use self::foo::{self, Foo, Bar};
"#,
&InsertUseConfig {
granularity: ImportGranularity::Crate,
prefix_kind: hir::PrefixKind::BySelf,
enforce_granularity: true,
group: true,
skip_glob_imports: true,
},
);
}
fn check_with_config(
path: &str,
ra_fixture_before: &str,

View file

@ -78,6 +78,10 @@ fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehav
{
lhs.split_prefix(&lhs_prefix);
rhs.split_prefix(&rhs_prefix);
} else {
ted::replace(lhs.syntax(), rhs.syntax());
// we can safely return here, in this case `recursive_merge` doesn't do anything
return Some(());
}
recursive_merge(lhs, rhs, merge)
}
@ -123,6 +127,13 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
// so they need to be handled explicitly
.or_else(|| tree.star_token().map(|_| false))
};
if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() {
ted::replace(lhs_t.syntax(), rhs_t.syntax());
*lhs_t = rhs_t;
continue;
}
match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
(Some(true), None) => continue,
(None, Some(true)) => {

View file

@ -31,26 +31,34 @@ pub fn items_with_name<'a>(
)
});
let prefix = matches!(name, NameToImport::Prefix(..));
let (mut local_query, mut external_query) = match name {
NameToImport::Exact(exact_name, case_sensitive) => {
NameToImport::Prefix(exact_name, case_sensitive)
| NameToImport::Exact(exact_name, case_sensitive) => {
let mut local_query = symbol_index::Query::new(exact_name.clone());
local_query.exact();
let external_query = import_map::Query::new(exact_name);
(
local_query,
if case_sensitive { external_query.case_sensitive() } else { external_query },
)
let mut external_query = import_map::Query::new(exact_name);
if prefix {
local_query.prefix();
external_query = external_query.prefix();
} else {
local_query.exact();
external_query = external_query.exact();
}
if case_sensitive {
local_query.case_sensitive();
external_query = external_query.case_sensitive();
}
(local_query, external_query)
}
NameToImport::Fuzzy(fuzzy_search_string) => {
NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => {
let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
local_query.fuzzy();
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
.fuzzy()
.assoc_search_mode(assoc_item_search);
if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
if case_sensitive {
local_query.case_sensitive();
external_query = external_query.case_sensitive();
}

View file

@ -43,13 +43,20 @@ use triomphe::Arc;
use crate::RootDatabase;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum SearchMode {
Fuzzy,
Exact,
Prefix,
}
#[derive(Debug)]
pub struct Query {
query: String,
lowercased: String,
only_types: bool,
libs: bool,
exact: bool,
mode: SearchMode,
case_sensitive: bool,
limit: usize,
}
@ -62,7 +69,7 @@ impl Query {
lowercased,
only_types: false,
libs: false,
exact: false,
mode: SearchMode::Fuzzy,
case_sensitive: false,
limit: usize::max_value(),
}
@ -76,8 +83,16 @@ impl Query {
self.libs = true;
}
pub fn fuzzy(&mut self) {
self.mode = SearchMode::Fuzzy;
}
pub fn exact(&mut self) {
self.exact = true;
self.mode = SearchMode::Exact;
}
pub fn prefix(&mut self) {
self.mode = SearchMode::Prefix;
}
pub fn case_sensitive(&mut self) {
@ -329,13 +344,23 @@ impl Query {
{
continue;
}
if self.exact {
if symbol.name != self.query {
continue;
let skip = match self.mode {
SearchMode::Fuzzy => {
self.case_sensitive
&& self.query.chars().any(|c| !symbol.name.contains(c))
}
} else if self.case_sensitive
&& self.query.chars().any(|c| !symbol.name.contains(c))
{
SearchMode::Exact => symbol.name != self.query,
SearchMode::Prefix if self.case_sensitive => {
!symbol.name.starts_with(&self.query)
}
SearchMode::Prefix => symbol
.name
.chars()
.zip(self.lowercased.chars())
.all(|(n, q)| n.to_lowercase().next() == Some(q)),
};
if skip {
continue;
}

View file

@ -1,4 +1,5 @@
//! Generates descriptors structure for unstable feature from Unstable Book
//! Generates descriptor structures for unstable features from the unstable book
//! and lints from rustc, rustdoc, and clippy.
use std::{borrow::Cow, fs, path::Path};
use itertools::Itertools;
@ -6,6 +7,8 @@ use stdx::format_to;
use test_utils::project_root;
use xshell::{cmd, Shell};
const DESTINATION: &str = "crates/ide-db/src/generated/lints.rs";
/// This clones rustc repo, and so is not worth to keep up-to-date. We update
/// manually by un-ignoring the test from time to time.
#[test]
@ -14,11 +17,21 @@ fn sourcegen_lint_completions() {
let sh = &Shell::new().unwrap();
let rust_repo = project_root().join("./target/rust");
if !rust_repo.exists() {
if rust_repo.exists() {
cmd!(sh, "git -C {rust_repo} pull --rebase").run().unwrap();
} else {
cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}")
.run()
.unwrap();
}
// need submodules for Cargo to parse the workspace correctly
cmd!(
sh,
"git -C {rust_repo} submodule update --init --recursive --depth=1 --
compiler library src/tools"
)
.run()
.unwrap();
let mut contents = String::from(
r"
@ -27,17 +40,28 @@ pub struct Lint {
pub label: &'static str,
pub description: &'static str,
}
pub struct LintGroup {
pub lint: Lint,
pub children: &'static [&'static str],
}
",
);
generate_lint_descriptor(sh, &mut contents);
contents.push('\n');
generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src"));
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let unstable_book = project_root().join("./target/unstable-book-gen");
cmd!(
sh,
"{cargo} run --manifest-path {rust_repo}/src/tools/unstable-book-gen/Cargo.toml --
{rust_repo}/library {rust_repo}/compiler {rust_repo}/src {unstable_book}"
)
.run()
.unwrap();
generate_feature_descriptor(&mut contents, &unstable_book.join("src"));
contents.push('\n');
let lints_json = project_root().join("./target/clippy_lints.json");
@ -51,41 +75,60 @@ pub struct LintGroup {
let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents));
let destination = project_root().join("crates/ide_db/src/generated/lints.rs");
let destination = project_root().join(DESTINATION);
sourcegen::ensure_file_contents(destination.as_path(), &contents);
}
/// Parses the output of `rustdoc -Whelp` and prints `Lint` and `LintGroup` constants into `buf`.
///
/// As of writing, the output of `rustc -Whelp` (not rustdoc) has the following format:
///
/// ```text
/// Lint checks provided by rustc:
///
/// name default meaning
/// ---- ------- -------
///
/// ...
///
/// Lint groups provided by rustc:
///
/// name sub-lints
/// ---- ---------
///
/// ...
/// ```
///
/// `rustdoc -Whelp` (and any other custom `rustc` driver) adds another two
/// tables after the `rustc` ones, with a different title but the same format.
fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
// FIXME: rustdoc currently requires an input file for -Whelp cc https://github.com/rust-lang/rust/pull/88831
let file = project_root().join(file!());
let stdout = cmd!(sh, "rustdoc -W help {file}").read().unwrap();
let start_lints = stdout.find("---- ------- -------").unwrap();
let start_lint_groups = stdout.find("---- ---------").unwrap();
let start_lints_rustdoc =
stdout.find("Lint checks provided by plugins loaded by this crate:").unwrap();
let start_lint_groups_rustdoc =
stdout.find("Lint groups provided by plugins loaded by this crate:").unwrap();
let stdout = cmd!(sh, "rustdoc -Whelp").read().unwrap();
let lints_pat = "---- ------- -------\n";
let lint_groups_pat = "---- ---------\n";
let lints = find_and_slice(&stdout, lints_pat);
let lint_groups = find_and_slice(lints, lint_groups_pat);
let lints_rustdoc = find_and_slice(lint_groups, lints_pat);
let lint_groups_rustdoc = find_and_slice(lints_rustdoc, lint_groups_pat);
buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#);
buf.push('\n');
let lints = stdout[start_lints..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
let lints = lints.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
});
let lint_groups =
stdout[start_lint_groups..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
});
let lint_groups = lint_groups.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
});
let lints = lints
.chain(lint_groups)
@ -94,7 +137,8 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
for (name, description, ..) in &lints {
push_lint_completion(buf, &name.replace('-', "_"), description);
}
buf.push_str("];\n");
buf.push_str("];\n\n");
buf.push_str(r#"pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints {
if !children.is_empty() {
@ -115,27 +159,23 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
buf.push_str(r#"pub const RUSTDOC_LINTS: &[Lint] = &["#);
buf.push('\n');
let lints_rustdoc =
stdout[start_lints_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) =
rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
});
let lints_rustdoc = lints_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
});
let lint_groups_rustdoc =
stdout[start_lint_groups_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(
|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
},
);
lint_groups_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
});
let lints_rustdoc = lints_rustdoc
.chain(lint_groups_rustdoc)
@ -145,7 +185,7 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
for (name, description, ..) in &lints_rustdoc {
push_lint_completion(buf, &name.replace('-', "_"), description)
}
buf.push_str("];\n");
buf.push_str("];\n\n");
buf.push_str(r#"pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints_rustdoc {
@ -157,14 +197,24 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
buf.push_str("];\n");
}
#[track_caller]
fn find_and_slice<'a>(i: &'a str, p: &str) -> &'a str {
let idx = i.find(p).unwrap();
&i[idx + p.len()..]
}
/// Parses the unstable book `src_dir` and prints a constant with the list of
/// unstable features into `buf`.
///
/// It does this by looking for all `.md` files in the `language-features` and
/// `library-features` directories, and using the file name as the feature
/// name, and the file contents as the feature description.
fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) {
let mut features = ["language-features", "library-features"]
.into_iter()
.flat_map(|it| sourcegen::list_files(&src_dir.join(it)))
.filter(|path| {
// Get all `.md ` files
path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md"
})
// Get all `.md` files
.filter(|path| path.extension() == Some("md".as_ref()))
.map(|path| {
let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace('-', "_");
let doc = fs::read_to_string(path).unwrap();
@ -196,7 +246,7 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
let mut clippy_lints: Vec<ClippyLint> = Vec::new();
let mut clippy_groups: std::collections::BTreeMap<String, Vec<String>> = Default::default();
for line in file_content.lines().map(|line| line.trim()) {
for line in file_content.lines().map(str::trim) {
if let Some(line) = line.strip_prefix(r#""id": ""#) {
let clippy_lint = ClippyLint {
id: line.strip_suffix(r#"","#).expect("should be suffixed by comma").into(),
@ -211,12 +261,19 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
.push(clippy_lints.last().unwrap().id.clone());
}
} else if let Some(line) = line.strip_prefix(r#""docs": ""#) {
let prefix_to_strip = r#" ### What it does"#;
let line = match line.strip_prefix(prefix_to_strip) {
Some(line) => line,
let header = "### What it does";
let line = match line.find(header) {
Some(idx) => &line[idx + header.len()..],
None => {
eprintln!("unexpected clippy prefix for {}", clippy_lints.last().unwrap().id);
continue;
let id = &clippy_lints.last().unwrap().id;
// these just don't have the common header
let allowed = ["allow_attributes", "read_line_without_trim"];
if allowed.contains(&id.as_str()) {
line
} else {
eprintln!("\nunexpected clippy prefix for {id}, line={line:?}\n",);
continue;
}
}
};
// Only take the description, any more than this is a lot of additional data we would embed into the exe

View file

@ -166,7 +166,7 @@ fn main() {
check_diagnostics(
r#"
struct A { a: &'static str }
fn f(a: A) { let A { a: hello } = a; }
fn f(a: A) { let A { a: _hello } = a; }
"#,
);
check_diagnostics(
@ -181,12 +181,14 @@ fn f(a: A) { let A { 0: 0 } = a; }
struct A { a: &'static str }
fn f(a: A) {
let A { a$0: a } = a;
_ = a;
}
"#,
r#"
struct A { a: &'static str }
fn f(a: A) {
let A { a } = a;
_ = a;
}
"#,
);
@ -196,12 +198,14 @@ fn f(a: A) {
struct A { a: &'static str, b: &'static str }
fn f(a: A) {
let A { a$0: a, b } = a;
_ = (a, b);
}
"#,
r#"
struct A { a: &'static str, b: &'static str }
fn f(a: A) {
let A { a, b } = a;
_ = (a, b);
}
"#,
);

View file

@ -111,6 +111,31 @@ fn some_fn() {
let what_aweird_formatting = 10;
another_func(what_aweird_formatting);
}
"#,
);
check_fix(
r#"
static S: i32 = M::A;
mod $0M {
pub const A: i32 = 10;
}
mod other {
use crate::M::A;
}
"#,
r#"
static S: i32 = m::A;
mod m {
pub const A: i32 = 10;
}
mod other {
use crate::m::A;
}
"#,
);
}
@ -175,10 +200,10 @@ fn NonSnakeCaseName() {}
fn incorrect_function_params() {
check_diagnostics(
r#"
fn foo(SomeParam: u8) {}
fn foo(SomeParam: u8) { _ = SomeParam; }
// ^^^^^^^^^ 💡 warn: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
fn foo2(ok_param: &str, CAPS_PARAM: u8) { _ = (ok_param, CAPS_PARAM); }
// ^^^^^^^^^^ 💡 warn: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
"#,
);
@ -188,6 +213,7 @@ fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
fn incorrect_variable_names() {
check_diagnostics(
r#"
#[allow(unused)]
fn foo() {
let SOME_VALUE = 10;
// ^^^^^^^^^^ 💡 warn: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
@ -294,6 +320,7 @@ impl someStruct {
// ^^^^^^^^ 💡 warn: Function `SomeFunc` should have snake_case name, e.g. `some_func`
let WHY_VAR_IS_CAPS = 10;
// ^^^^^^^^^^^^^^^ 💡 warn: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
_ = WHY_VAR_IS_CAPS;
}
}
"#,
@ -306,6 +333,7 @@ impl someStruct {
r#"
enum Option { Some, None }
#[allow(unused)]
fn main() {
match Option::None {
None => (),
@ -322,6 +350,7 @@ fn main() {
r#"
enum Option { Some, None }
#[allow(unused)]
fn main() {
match Option::None {
SOME_VAR @ None => (),
@ -349,7 +378,9 @@ enum E {
}
mod F {
fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {
_ = BAD_NAME_HI;
}
}
"#,
);
@ -395,7 +426,7 @@ fn qualify() {
#[test] // Issue #8809.
fn parenthesized_parameter() {
check_diagnostics(r#"fn f((O): _) {}"#)
check_diagnostics(r#"fn f((O): _) { _ = O; }"#)
}
#[test]
@ -472,7 +503,9 @@ mod CheckBadStyle {
mod F {
#![allow(non_snake_case)]
fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {
_ = BAD_NAME_HI;
}
}
#[allow(non_snake_case, non_camel_case_types)]
@ -510,17 +543,20 @@ fn NonSnakeCaseName(some_var: u8) -> u8 {
#[deny(nonstandard_style)]
mod CheckNonstandardStyle {
//^^^^^^^^^^^^^^^^^^^^^ 💡 error: Module `CheckNonstandardStyle` should have snake_case name, e.g. `check_nonstandard_style`
fn HiImABadFnName() {}
//^^^^^^^^^^^^^^ 💡 error: Function `HiImABadFnName` should have snake_case name, e.g. `hi_im_abad_fn_name`
}
#[deny(warnings)]
mod CheckBadStyle {
//^^^^^^^^^^^^^ 💡 error: Module `CheckBadStyle` should have snake_case name, e.g. `check_bad_style`
struct fooo;
//^^^^ 💡 error: Structure `fooo` should have CamelCase name, e.g. `Fooo`
}
mod F {
//^ 💡 warn: Module `F` should have snake_case name, e.g. `f`
#![deny(non_snake_case)]
fn CheckItWorksWithModAttr() {}
//^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: Function `CheckItWorksWithModAttr` should have snake_case name, e.g. `check_it_works_with_mod_attr`
@ -641,4 +677,30 @@ enum E {
"#,
);
}
#[test]
fn module_name_inline() {
check_diagnostics(
r#"
mod M {
//^ 💡 warn: Module `M` should have snake_case name, e.g. `m`
mod IncorrectCase {}
//^^^^^^^^^^^^^ 💡 warn: Module `IncorrectCase` should have snake_case name, e.g. `incorrect_case`
}
"#,
);
}
#[test]
fn module_name_decl() {
check_diagnostics(
r#"
//- /Foo.rs
//- /main.rs
mod Foo;
//^^^ 💡 warn: Module `Foo` should have snake_case name, e.g. `foo`
"#,
)
}
}

View file

@ -23,12 +23,7 @@ pub(crate) fn mismatched_tuple_struct_pat_arg_count(
Diagnostic::new(
DiagnosticCode::RustcHardError("E0023"),
message,
invalid_args_range(
ctx,
d.expr_or_pat.clone().map(|it| it.either(Into::into, Into::into)),
d.expected,
d.found,
),
invalid_args_range(ctx, d.expr_or_pat.clone().map(Into::into), d.expected, d.found),
)
}
@ -131,7 +126,7 @@ fn f() { zero(); }
fn simple_free_fn_one() {
check_diagnostics(
r#"
fn one(arg: u8) {}
fn one(_arg: u8) {}
fn f() { one(); }
//^^ error: expected 1 argument, found 0
"#,
@ -139,7 +134,7 @@ fn f() { one(); }
check_diagnostics(
r#"
fn one(arg: u8) {}
fn one(_arg: u8) {}
fn f() { one(1); }
"#,
);
@ -176,7 +171,7 @@ fn f() {
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
impl S { fn method(&self, _arg: u8) {} }
fn f() {
S.method();
@ -187,7 +182,7 @@ impl S { fn method(&self, arg: u8) {} }
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
impl S { fn method(&self, _arg: u8) {} }
fn f() {
S::method(&S, 0);
@ -335,8 +330,8 @@ struct S;
impl S {
fn method(#[cfg(NEVER)] self) {}
fn method2(#[cfg(NEVER)] self, arg: u8) {}
fn method3(self, #[cfg(NEVER)] arg: u8) {}
fn method2(#[cfg(NEVER)] self, _arg: u8) {}
fn method3(self, #[cfg(NEVER)] _arg: u8) {}
}
extern "C" {
@ -365,8 +360,8 @@ fn main() {
r#"
#[rustc_legacy_const_generics(1, 3)]
fn mixed<const N1: &'static str, const N2: bool>(
a: u8,
b: i8,
_a: u8,
_b: i8,
) {}
fn f() {
@ -376,8 +371,8 @@ fn f() {
#[rustc_legacy_const_generics(1, 3)]
fn b<const N1: u8, const N2: u8>(
a: u8,
b: u8,
_a: u8,
_b: u8,
) {}
fn g() {
@ -403,7 +398,7 @@ fn f(
// ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 2 fields
S(e, f, .., g, d): S
// ^^^^^^^^^ error: this pattern has 4 fields, but the corresponding tuple struct has 2 fields
) {}
) { _ = (a, b, c, d, e, f, g); }
"#,
)
}

View file

@ -39,7 +39,7 @@ pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingField
d.field_list_parent_path
.clone()
.map(SyntaxNodePtr::from)
.unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
.unwrap_or_else(|| d.field_list_parent.clone().into()),
);
Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError("E0063"), message, ptr)
@ -58,10 +58,8 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
let root = ctx.sema.db.parse_or_expand(d.file);
let current_module = match &d.field_list_parent {
Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
Either::Right(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
};
let current_module =
ctx.sema.scope(d.field_list_parent.to_node(&root).syntax()).map(|it| it.module());
let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
let edit = {
@ -87,9 +85,8 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
)])
};
match &d.field_list_parent {
Either::Left(record_expr) => {
let field_list_parent = record_expr.to_node(&root);
match &d.field_list_parent.to_node(&root) {
Either::Left(field_list_parent) => {
let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent);
let mut locals = FxHashMap::default();
@ -152,8 +149,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
old_field_list.syntax(),
)
}
Either::Right(record_pat) => {
let field_list_parent = record_pat.to_node(&root);
Either::Right(field_list_parent) => {
let missing_fields = ctx.sema.record_pattern_missing_fields(&field_list_parent);
let old_field_list = field_list_parent.record_pat_field_list()?;
@ -290,6 +286,7 @@ fn x(a: S) {
struct S { s: u32 }
fn x(a: S) {
let S { ref s } = a;
_ = s;
}
",
)
@ -626,7 +623,7 @@ struct TestStruct { one: i32, two: i64 }
fn test_fn() {
let one = 1;
let s = TestStruct{ one, two: 2 };
let _s = TestStruct{ one, two: 2 };
}
"#,
);

View file

@ -19,6 +19,7 @@ pub(crate) fn missing_match_arms(
mod tests {
use crate::tests::check_diagnostics;
#[track_caller]
fn check_diagnostics_no_bails(ra_fixture: &str) {
cov_mark::check_count!(validate_match_bailed_out, 0);
crate::tests::check_diagnostics(ra_fixture)
@ -564,6 +565,7 @@ fn bang(never: !) {
r#"
enum Option<T> { Some(T), None }
#[allow(unused)]
fn main() {
// `Never` is deliberately not defined so that it's an uninferred type.
match Option::<Never>::None {
@ -719,7 +721,7 @@ fn main() {
r#"
struct S { a: char}
fn main(v: S) {
match v { S{ a } => {} }
match v { S{ a } => { _ = a; } }
match v { S{ a: _x } => {} }
match v { S{ a: 'a' } => {} }
match v { S{..} => {} }
@ -901,7 +903,7 @@ enum E{ A, B }
fn foo() {
match &E::A {
E::A => {}
x => {}
_x => {}
}
}",
);

View file

@ -100,9 +100,9 @@ mod tests {
r#"
fn main() {
let x = &5 as *const usize;
unsafe { let y = *x; }
let z = *x;
} //^^💡 error: this operation is unsafe and requires an unsafe function or block
unsafe { let _y = *x; }
let _z = *x;
} //^^💡 error: this operation is unsafe and requires an unsafe function or block
"#,
)
}
@ -116,13 +116,13 @@ struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
let _y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
let _y = *x;
}
fn main() {
@ -152,10 +152,10 @@ struct Ty {
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT.a;
//^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
let _x = STATIC_MUT.a;
//^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
unsafe {
let x = STATIC_MUT.a;
let _x = STATIC_MUT.a;
}
}
"#,
@ -187,13 +187,13 @@ fn main() {
r#"
fn main() {
let x = &5 as *const usize;
let z = *x$0;
let _z = *x$0;
}
"#,
r#"
fn main() {
let x = &5 as *const usize;
let z = unsafe { *x };
let _z = unsafe { *x };
}
"#,
);
@ -231,7 +231,7 @@ struct S(usize);
impl S {
unsafe fn func(&self) {
let x = &self.0 as *const usize;
let z = *x;
let _z = *x;
}
}
fn main() {
@ -244,7 +244,7 @@ struct S(usize);
impl S {
unsafe fn func(&self) {
let x = &self.0 as *const usize;
let z = *x;
let _z = *x;
}
}
fn main() {
@ -267,7 +267,7 @@ struct Ty {
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT$0.a;
let _x = STATIC_MUT$0.a;
}
"#,
r#"
@ -278,7 +278,7 @@ struct Ty {
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = unsafe { STATIC_MUT.a };
let _x = unsafe { STATIC_MUT.a };
}
"#,
)
@ -382,16 +382,16 @@ fn main() {
static mut STATIC_MUT: u8 = 0;
fn main() {
let x;
x = STATIC_MUT$0;
let _x;
_x = STATIC_MUT$0;
}
"#,
r#"
static mut STATIC_MUT: u8 = 0;
fn main() {
let x;
x = unsafe { STATIC_MUT };
let _x;
_x = unsafe { STATIC_MUT };
}
"#,
)
@ -405,14 +405,14 @@ fn main() {
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = STATIC_MUT$0 + 1;
let _x = STATIC_MUT$0 + 1;
}
"#,
r#"
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = unsafe { STATIC_MUT } + 1;
let _x = unsafe { STATIC_MUT } + 1;
}
"#,
)
@ -425,14 +425,14 @@ fn main() {
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = &STATIC_MUT$0;
let _x = &STATIC_MUT$0;
}
"#,
r#"
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = unsafe { &STATIC_MUT };
let _x = unsafe { &STATIC_MUT };
}
"#,
)
@ -445,14 +445,14 @@ fn main() {
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = &&STATIC_MUT$0;
let _x = &&STATIC_MUT$0;
}
"#,
r#"
static mut STATIC_MUT: u8 = 0;
fn main() {
let x = unsafe { &&STATIC_MUT };
let _x = unsafe { &&STATIC_MUT };
}
"#,
)

View file

@ -29,6 +29,7 @@ fn main() {
let a = &X;
let b = *a;
//^ error: cannot move `X` out of reference
_ = b;
}
"#,
);
@ -46,6 +47,7 @@ fn main() {
let b = a.0;
//^ error: cannot move `X` out of reference
let y = a.1;
_ = (b, y);
}
"#,
);
@ -59,8 +61,8 @@ fn main() {
struct X;
fn main() {
static S: X = X;
let s = S;
//^ error: cannot move `X` out of reference
let _s = S;
//^^ error: cannot move `X` out of reference
}
"#,
);
@ -165,7 +167,7 @@ enum X {
fn main() {
let x = &X::Bar;
let c = || match *x {
let _c = || match *x {
X::Foo(t) => t,
_ => 5,
};
@ -173,4 +175,19 @@ fn main() {
"#,
);
}
#[test]
fn regression_15787() {
check_diagnostics(
r#"
//- minicore: coerce_unsized, slice, copy
fn foo(mut slice: &[u32]) -> usize {
slice = match slice {
[0, rest @ ..] | rest => rest,
};
slice.len()
}
"#,
);
}
}

View file

@ -324,6 +324,7 @@ fn main() {
let x_own = 2;
let ref mut x_ref = x_own;
//^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x_own`
_ = x_ref;
}
"#,
);
@ -331,7 +332,7 @@ fn main() {
r#"
struct Foo;
impl Foo {
fn method(&mut self, x: i32) {}
fn method(&mut self, _x: i32) {}
}
fn main() {
let x = Foo;
@ -391,6 +392,7 @@ fn main() {
//^^^^^ 💡 warn: variable does not need to be mutable
x = 7;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
_ = y;
}
}
}
@ -404,12 +406,14 @@ fn main() {
// there would be no mutability error for locals in dead code. Rustc tries to
// not emit `unused_mut` in this case, but since it works without `mut`, and
// special casing it is not trivial, we emit it.
// Update: now MIR based `unused-variable` is taking over `unused-mut` for the same reason.
check_diagnostics(
r#"
fn main() {
return;
let mut x = 2;
//^^^^^ 💡 warn: variable does not need to be mutable
//^^^^^ warn: unused variable
&mut x;
}
"#,
@ -419,7 +423,7 @@ fn main() {
fn main() {
loop {}
let mut x = 2;
//^^^^^ 💡 warn: variable does not need to be mutable
//^^^^^ warn: unused variable
&mut x;
}
"#,
@ -440,7 +444,7 @@ fn main(b: bool) {
g();
}
let mut x = 2;
//^^^^^ 💡 warn: variable does not need to be mutable
//^^^^^ warn: unused variable
&mut x;
}
"#,
@ -454,7 +458,7 @@ fn main(b: bool) {
return;
}
let mut x = 2;
//^^^^^ 💡 warn: variable does not need to be mutable
//^^^^^ warn: unused variable
&mut x;
}
"#,
@ -536,6 +540,7 @@ fn main() {
(k @ 5, ref mut t) if { continue; } => {
//^^^^^^^^^ 💡 error: cannot mutate immutable variable `z`
*t = 5;
_ = k;
}
_ => {
let y = (1, 2);
@ -588,6 +593,7 @@ fn main() {
b = 1;
c = (2, 3);
d = 3;
_ = (c, b, d);
}
}
"#,
@ -600,6 +606,7 @@ fn main() {
r#"
fn f(mut x: i32) {
//^^^^^ 💡 warn: variable does not need to be mutable
f(x + 2);
}
"#,
);
@ -615,8 +622,11 @@ fn f(x: i32) {
r#"
fn f((x, y): (i32, i32)) {
let t = [0; 2];
x = 5;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
x = 5;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
_ = x;
_ = y;
_ = t;
}
"#,
);
@ -645,6 +655,7 @@ fn f(x: [(i32, u8); 10]) {
//^^^^^ 💡 warn: variable does not need to be mutable
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
_ = b;
}
}
"#,
@ -666,6 +677,7 @@ fn f(x: [(i32, u8); 10]) {
//^^^^^ 💡 error: cannot mutate immutable variable `a`
c = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `c`
_ = (b, d);
}
}
}
@ -696,18 +708,18 @@ fn f() {
fn overloaded_index() {
check_diagnostics(
r#"
//- minicore: index
//- minicore: index, copy
use core::ops::{Index, IndexMut};
struct Foo;
impl Index<usize> for Foo {
type Output = (i32, u8);
fn index(&self, index: usize) -> &(i32, u8) {
fn index(&self, _index: usize) -> &(i32, u8) {
&(5, 2)
}
}
impl IndexMut<usize> for Foo {
fn index_mut(&mut self, index: usize) -> &mut (i32, u8) {
fn index_mut(&mut self, _index: usize) -> &mut (i32, u8) {
&mut (5, 2)
}
}
@ -715,26 +727,32 @@ fn f() {
let mut x = Foo;
//^^^^^ 💡 warn: variable does not need to be mutable
let y = &x[2];
_ = (x, y);
let x = Foo;
let y = &mut x[2];
//^💡 error: cannot mutate immutable variable `x`
_ = (x, y);
let mut x = &mut Foo;
//^^^^^ 💡 warn: variable does not need to be mutable
let y: &mut (i32, u8) = &mut x[2];
_ = (x, y);
let x = Foo;
let ref mut y = x[7];
//^ 💡 error: cannot mutate immutable variable `x`
_ = (x, y);
let (ref mut y, _) = x[3];
//^ 💡 error: cannot mutate immutable variable `x`
_ = y;
match x[10] {
//^ 💡 error: cannot mutate immutable variable `x`
(ref y, _) => (),
(_, ref mut y) => (),
(ref y, 5) => _ = y,
(_, ref mut y) => _ = y,
}
let mut x = Foo;
let mut i = 5;
//^^^^^ 💡 warn: variable does not need to be mutable
let y = &mut x[i];
_ = y;
}
"#,
);
@ -744,7 +762,7 @@ fn f() {
fn overloaded_deref() {
check_diagnostics(
r#"
//- minicore: deref_mut
//- minicore: deref_mut, copy
use core::ops::{Deref, DerefMut};
struct Foo;
@ -763,21 +781,27 @@ fn f() {
let mut x = Foo;
//^^^^^ 💡 warn: variable does not need to be mutable
let y = &*x;
_ = (x, y);
let x = Foo;
let y = &mut *x;
//^^ 💡 error: cannot mutate immutable variable `x`
_ = (x, y);
let x = Foo;
//^ warn: unused variable
let x = Foo;
let y: &mut (i32, u8) = &mut x;
//^^^^^^ 💡 error: cannot mutate immutable variable `x`
_ = (x, y);
let ref mut y = *x;
//^^ 💡 error: cannot mutate immutable variable `x`
_ = y;
let (ref mut y, _) = *x;
//^^ 💡 error: cannot mutate immutable variable `x`
_ = y;
match *x {
//^^ 💡 error: cannot mutate immutable variable `x`
(ref y, _) => (),
(_, ref mut y) => (),
(ref y, 5) => _ = y,
(_, ref mut y) => _ = y,
}
}
"#,
@ -866,6 +890,7 @@ pub fn test() {
data: 0
}
);
_ = tree;
}
"#,
);
@ -925,6 +950,7 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
let x = X;
let closure4 = || { x.mutate(); };
//^ 💡 error: cannot mutate immutable variable `x`
_ = (closure2, closure3, closure4);
}
"#,
);
@ -941,7 +967,9 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
z = 3;
let mut k = z;
//^^^^^ 💡 warn: variable does not need to be mutable
_ = k;
};
_ = (x, closure);
}
"#,
);
@ -958,6 +986,7 @@ fn f() {
}
}
};
_ = closure;
}
"#,
);
@ -972,7 +1001,8 @@ fn f() {
let mut x = X;
let c2 = || { x = X; x };
let mut x = X;
let c2 = move || { x = X; };
let c3 = move || { x = X; };
_ = (c1, c2, c3);
}
"#,
);
@ -1023,7 +1053,7 @@ fn x(t: &[u8]) {
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
_ = b;
}
_ => {}
}
@ -1079,6 +1109,7 @@ fn f() {
let x = Box::new(5);
let closure = || *x = 2;
//^ 💡 error: cannot mutate immutable variable `x`
_ = closure;
}
"#,
);
@ -1156,6 +1187,7 @@ macro_rules! mac {
fn main2() {
let mut x = mac![];
//^^^^^ 💡 warn: variable does not need to be mutable
_ = x;
}
"#,
);

View file

@ -13,7 +13,7 @@ use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
//
// This diagnostic is triggered if created structure does not have field provided in record.
pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
let node = d.field.clone().map(|it| it.either(Into::into, Into::into));
let node = d.field.clone().map(Into::into);
if d.private {
// FIXME: quickfix to add required visibility
Diagnostic::new_with_syntax_node_ptr(
@ -35,15 +35,13 @@ pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField)
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
// FIXME: quickfix for pattern
match &d.field.value {
Either::Left(ptr) => {
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
missing_record_expr_field_fixes(
&ctx.sema,
d.field.file_id.original_file(ctx.sema.db),
&ptr.to_node(&root),
)
}
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
match &d.field.value.to_node(&root) {
Either::Left(node) => missing_record_expr_field_fixes(
&ctx.sema,
d.field.file_id.original_file(ctx.sema.db),
node,
),
_ => None,
}
}

View file

@ -1,5 +1,3 @@
use either::Either;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: private-assoc-item
@ -28,13 +26,7 @@ pub(crate) fn private_assoc_item(
},
name,
),
d.expr_or_pat.clone().map(|it| match it {
Either::Left(it) => it.into(),
Either::Right(it) => match it {
Either::Left(it) => it.into(),
Either::Right(it) => it.into(),
},
}),
d.expr_or_pat.clone().map(Into::into),
)
}

View file

@ -74,8 +74,8 @@ mod tests {
r#"
//- minicore: iterators
fn foo() {
let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
"#,
);
}
@ -117,7 +117,7 @@ fn foo() {
fn foo() {
let mut m = core::iter::repeat(())
.filter_map(|()| Some(92));
let n = m.next();
let _n = m.next();
}
"#,
);
@ -148,22 +148,22 @@ fn foo() {
fn foo() {
#[allow(clippy::filter_map_next)]
let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
}
#[deny(clippy::filter_map_next)]
fn foo() {
let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
fn foo() {
let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
#[warn(clippy::filter_map_next)]
fn foo() {
let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
let _m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
"#,
);

View file

@ -1,4 +1,3 @@
use either::Either;
use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, InFile, Type};
use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
use syntax::{
@ -14,9 +13,11 @@ use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticCode, Dia
// This diagnostic is triggered when the type of an expression or pattern does not match
// the expected type.
pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
let display_range = match &d.expr_or_pat {
Either::Left(expr) => {
adjusted_display_range::<ast::Expr>(ctx, expr.clone().map(|it| it.into()), &|expr| {
let display_range = match &d.expr_or_pat.value {
expr if ast::Expr::can_cast(expr.kind()) => adjusted_display_range::<ast::Expr>(
ctx,
InFile { file_id: d.expr_or_pat.file_id, value: expr.syntax_node_ptr() },
&|expr| {
let salient_token_range = match expr {
ast::Expr::IfExpr(it) => it.if_token()?.text_range(),
ast::Expr::LoopExpr(it) => it.loop_token()?.text_range(),
@ -32,10 +33,15 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
cov_mark::hit!(type_mismatch_range_adjustment);
Some(salient_token_range)
})
}
Either::Right(pat) => {
ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range
},
),
pat => {
ctx.sema
.diagnostics_display_range(InFile {
file_id: d.expr_or_pat.file_id,
value: pat.syntax_node_ptr(),
})
.range
}
};
let mut diag = Diagnostic::new(
@ -57,14 +63,12 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> {
let mut fixes = Vec::new();
match &d.expr_or_pat {
Either::Left(expr_ptr) => {
add_reference(ctx, d, expr_ptr, &mut fixes);
add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
remove_semicolon(ctx, d, expr_ptr, &mut fixes);
str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
}
Either::Right(_pat_ptr) => {}
if let Some(expr_ptr) = d.expr_or_pat.value.clone().cast::<ast::Expr>() {
let expr_ptr = &InFile { file_id: d.expr_or_pat.file_id, value: expr_ptr.clone() };
add_reference(ctx, d, expr_ptr, &mut fixes);
add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
remove_semicolon(ctx, d, expr_ptr, &mut fixes);
str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
}
if fixes.is_empty() {
@ -205,7 +209,7 @@ fn main() {
test(123);
//^^^ 💡 error: expected &i32, found i32
}
fn test(arg: &i32) {}
fn test(_arg: &i32) {}
"#,
);
}
@ -217,13 +221,13 @@ fn test(arg: &i32) {}
fn main() {
test(123$0);
}
fn test(arg: &i32) {}
fn test(_arg: &i32) {}
"#,
r#"
fn main() {
test(&123);
}
fn test(arg: &i32) {}
fn test(_arg: &i32) {}
"#,
);
}
@ -235,13 +239,13 @@ fn test(arg: &i32) {}
fn main() {
test($0123);
}
fn test(arg: &mut i32) {}
fn test(_arg: &mut i32) {}
"#,
r#"
fn main() {
test(&mut 123);
}
fn test(arg: &mut i32) {}
fn test(_arg: &mut i32) {}
"#,
);
}
@ -254,13 +258,13 @@ fn test(arg: &mut i32) {}
fn main() {
test($0[1, 2, 3]);
}
fn test(arg: &[i32]) {}
fn test(_arg: &[i32]) {}
"#,
r#"
fn main() {
test(&[1, 2, 3]);
}
fn test(arg: &[i32]) {}
fn test(_arg: &[i32]) {}
"#,
);
}
@ -279,7 +283,7 @@ impl core::ops::Deref for Foo {
fn main() {
test($0Foo);
}
fn test(arg: &Bar) {}
fn test(_arg: &Bar) {}
"#,
r#"
struct Foo;
@ -291,7 +295,7 @@ impl core::ops::Deref for Foo {
fn main() {
test(&Foo);
}
fn test(arg: &Bar) {}
fn test(_arg: &Bar) {}
"#,
);
}
@ -305,7 +309,7 @@ fn main() {
}
struct Test;
impl Test {
fn call_by_ref(&self, arg: &i32) {}
fn call_by_ref(&self, _arg: &i32) {}
}
"#,
r#"
@ -314,7 +318,7 @@ fn main() {
}
struct Test;
impl Test {
fn call_by_ref(&self, arg: &i32) {}
fn call_by_ref(&self, _arg: &i32) {}
}
"#,
);
@ -345,7 +349,7 @@ macro_rules! thousand {
1000_u64
};
}
fn test(foo: &u64) {}
fn test(_foo: &u64) {}
fn main() {
test($0thousand!());
}
@ -356,7 +360,7 @@ macro_rules! thousand {
1000_u64
};
}
fn test(foo: &u64) {}
fn test(_foo: &u64) {}
fn main() {
test(&thousand!());
}
@ -369,12 +373,12 @@ fn main() {
check_fix(
r#"
fn main() {
let test: &mut i32 = $0123;
let _test: &mut i32 = $0123;
}
"#,
r#"
fn main() {
let test: &mut i32 = &mut 123;
let _test: &mut i32 = &mut 123;
}
"#,
);
@ -411,7 +415,7 @@ fn div(x: i32, y: i32) -> Option<i32> {
fn f<const N: u64>() -> Rate<N> { // FIXME: add some error
loop {}
}
fn run(t: Rate<5>) {
fn run(_t: Rate<5>) {
}
fn main() {
run(f()) // FIXME: remove this error
@ -426,7 +430,7 @@ fn div(x: i32, y: i32) -> Option<i32> {
check_diagnostics(
r#"
pub struct Rate<T, const NOM: u32, const DENOM: u32>(T);
fn run(t: Rate<u32, 1, 1>) {
fn run(_t: Rate<u32, 1, 1>) {
}
fn main() {
run(Rate::<_, _, _>(5));
@ -650,7 +654,7 @@ fn h() {
r#"
struct X<T>(T);
fn foo(x: X<Unknown>) {}
fn foo(_x: X<Unknown>) {}
fn test1() {
// Unknown might be `i32`, so we should not emit type mismatch here.
foo(X(42));

View file

@ -142,8 +142,8 @@ fn t<T>() -> T { loop {} }
check_diagnostics(
r#"
fn main() {
let x = [(); _];
let y: [(); 10] = [(); _];
let _x = [(); _];
let _y: [(); 10] = [(); _];
_ = 0;
(_,) = (1,);
}

View file

@ -0,0 +1,111 @@
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unused-variables
//
// This diagnostic is triggered when a local variable is not used.
pub(crate) fn unused_variables(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnusedVariable,
) -> Diagnostic {
let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcLint("unused_variables"),
"unused variable",
ast,
)
.experimental()
}
#[cfg(test)]
mod tests {
use crate::tests::check_diagnostics;
#[test]
fn unused_variables_simple() {
check_diagnostics(
r#"
//- minicore: fn
struct Foo { f1: i32, f2: i64 }
fn f(kkk: i32) {}
//^^^ warn: unused variable
fn main() {
let a = 2;
//^ warn: unused variable
let b = 5;
// note: `unused variable` implies `unused mut`, so we should not emit both at the same time.
let mut c = f(b);
//^^^^^ warn: unused variable
let (d, e) = (3, 5);
//^ warn: unused variable
let _ = e;
let f1 = 2;
let f2 = 5;
let f = Foo { f1, f2 };
match f {
Foo { f1, f2 } => {
//^^ warn: unused variable
_ = f2;
}
}
let g = false;
if g {}
let h: fn() -> i32 = || 2;
let i = h();
//^ warn: unused variable
}
"#,
);
}
#[test]
fn unused_self() {
check_diagnostics(
r#"
struct S {
}
impl S {
fn owned_self(self, u: i32) {}
//^ warn: unused variable
fn ref_self(&self, u: i32) {}
//^ warn: unused variable
fn ref_mut_self(&mut self, u: i32) {}
//^ warn: unused variable
fn owned_mut_self(mut self) {}
//^^^^^^^^ 💡 warn: variable does not need to be mutable
}
"#,
);
}
#[test]
fn allow_unused_variables_for_identifiers_starting_with_underline() {
check_diagnostics(
r#"
fn main() {
let _x = 2;
}
"#,
);
}
#[test]
fn respect_lint_attributes_for_unused_variables() {
check_diagnostics(
r#"
fn main() {
#[allow(unused_variables)]
let x = 2;
}
#[deny(unused)]
fn main2() {
let x = 2;
//^ error: unused variable
}
"#,
);
}
}

View file

@ -56,6 +56,7 @@ mod handlers {
pub(crate) mod unresolved_proc_macro;
pub(crate) mod undeclared_label;
pub(crate) mod unreachable_label;
pub(crate) mod unused_variables;
// The handlers below are unusual, the implement the diagnostics as well.
pub(crate) mod field_shorthand;
@ -368,6 +369,7 @@ pub fn diagnostics(
AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d),
AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled),
AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
AnyDiagnostic::UnusedVariable(d) => handlers::unused_variables::unused_variables(&ctx, &d),
AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
AnyDiagnostic::MismatchedTupleStructPatArgCount(d) => handlers::mismatched_arg_count::mismatched_tuple_struct_pat_arg_count(&ctx, &d),
};

View file

@ -602,7 +602,17 @@ fn filename_and_frag_for_def(
}
Definition::Const(c) => format!("const.{}.html", c.name(db)?.display(db.upcast())),
Definition::Static(s) => format!("static.{}.html", s.name(db).display(db.upcast())),
Definition::Macro(mac) => format!("macro.{}.html", mac.name(db).display(db.upcast())),
Definition::Macro(mac) => match mac.kind(db) {
hir::MacroKind::Declarative
| hir::MacroKind::BuiltIn
| hir::MacroKind::Attr
| hir::MacroKind::ProcMacro => {
format!("macro.{}.html", mac.name(db).display(db.upcast()))
}
hir::MacroKind::Derive => {
format!("derive.{}.html", mac.name(db).display(db.upcast()))
}
},
Definition::Field(field) => {
let def = match field.parent_def(db) {
hir::VariantDef::Struct(it) => Definition::Adt(it.into()),

View file

@ -180,26 +180,24 @@ fn hover_simple(
descended()
.filter_map(|token| {
let node = token.parent()?;
let class = IdentClass::classify_token(sema, token)?;
if let IdentClass::Operator(OperatorClass::Await(_)) = class {
match IdentClass::classify_node(sema, &node)? {
// It's better for us to fall back to the keyword hover here,
// rendering poll is very confusing
return None;
IdentClass::Operator(OperatorClass::Await(_)) => None,
IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
decl,
..
}) => Some(vec![(Definition::ExternCrateDecl(decl), node)]),
class => Some(
class
.definitions()
.into_iter()
.zip(iter::repeat(node))
.collect::<Vec<_>>(),
),
}
if let IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
decl,
..
}) = class
{
return Some(vec![(Definition::ExternCrateDecl(decl), node)]);
}
Some(
class
.definitions()
.into_iter()
.zip(iter::once(node).cycle())
.collect::<Vec<_>>(),
)
})
.flatten()
.unique_by(|&(def, _)| def)

View file

@ -402,10 +402,9 @@ pub(super) fn definition(
|&it| it.layout(db),
|_| {
let var_def = it.parent_def(db);
let id = it.index();
match var_def {
hir::VariantDef::Struct(s) => {
Adt::from(s).layout(db).ok().and_then(|layout| layout.field_offset(id))
Adt::from(s).layout(db).ok().and_then(|layout| layout.field_offset(it))
}
_ => None,
}

View file

@ -5266,38 +5266,46 @@ pub fn foo() {}
#[test]
fn hover_feature() {
check(
r#"#![feature(box_syntax$0)]"#,
expect![[r##"
*box_syntax*
```
box_syntax
```
___
r#"#![feature(intrinsics$0)]"#,
expect![[r#"
*intrinsics*
```
intrinsics
```
___
# `box_syntax`
# `intrinsics`
The tracking issue for this feature is: [#49733]
The tracking issue for this feature is: None.
[#49733]: https://github.com/rust-lang/rust/issues/49733
Intrinsics are never intended to be stable directly, but intrinsics are often
exported in some sort of stable manner. Prefer using the stable interfaces to
the intrinsic directly when you can.
See also [`box_patterns`](box-patterns.md)
------------------------
------------------------
Currently the only stable way to create a `Box` is via the `Box::new` method.
Also it is not possible in stable Rust to destructure a `Box` in a match
pattern. The unstable `box` keyword can be used to create a `Box`. An example
usage would be:
These are imported as if they were FFI functions, with the special
`rust-intrinsic` ABI. For example, if one was in a freestanding
context, but wished to be able to `transmute` between types, and
perform efficient pointer arithmetic, one would import those functions
via a declaration like
```rust
#![feature(box_syntax)]
```rust
#![feature(intrinsics)]
#![allow(internal_features)]
# fn main() {}
fn main() {
let b = box 5;
}
```
extern "rust-intrinsic" {
fn transmute<T, U>(x: T) -> U;
"##]],
fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
}
```
As with any other FFI functions, these are always `unsafe` to call.
"#]],
)
}

Some files were not shown because too many files have changed in this diff Show more