Auto merge of #150015 - lnicola:sync-from-ra, r=lnicola

`rust-analyzer` subtree update

Subtree update of `rust-analyzer` to https://github.com/rust-lang/rust-analyzer/commit/3d78b3f9e00dd8ca76ea31f01edbf93d41da98bb.

Created using https://github.com/rust-lang/josh-sync.

r? `@ghost`
This commit is contained in:
bors
2025-12-15 15:20:36 +00:00
75 changed files with 3068 additions and 1495 deletions
@@ -64,6 +64,28 @@ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
};
}
/// # SAFETY
///
/// `old_pointer` must be valid for unique writes
pub unsafe fn unsafe_update_eq<T>(old_pointer: *mut T, new_value: T) -> bool
where
T: PartialEq,
{
// SAFETY: Caller obligation
let old_ref: &mut T = unsafe { &mut *old_pointer };
if *old_ref != new_value {
*old_ref = new_value;
true
} else {
// Subtle but important: Eq impls can be buggy or define equality
// in surprising ways. If it says that the value has not changed,
// we do not modify the existing value, and thus do not have to
// update the revision, as downstream code will not see the new value.
false
}
}
pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16;
pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
@@ -155,6 +155,9 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow<Infal
"rustc_skip_during_method_dispatch" => {
extract_rustc_skip_during_method_dispatch(attr_flags, tt)
}
"rustc_deprecated_safe_2024" => {
attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024)
}
_ => {}
},
2 => match path.segments[0].text() {
@@ -8,12 +8,12 @@
use triomphe::Arc;
use crate::{
AssocItemId, AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, EnumVariantId,
EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId,
FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalFieldId, Macro2Id, Macro2Loc, MacroExpander,
MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ProcMacroId, ProcMacroLoc, StaticId,
StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId,
UnionLoc, UseId, UseLoc, VariantId,
AssocItemId, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc,
EnumVariantId, EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc,
FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalFieldId, Macro2Id, Macro2Loc,
MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ProcMacroId,
ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId,
TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId,
attrs::AttrFlags,
expr_store::{
Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, scope::ExprScopes,
@@ -82,6 +82,9 @@ pub trait InternDatabase: RootQueryDb {
#[salsa::interned]
fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId;
// endregion: items
#[salsa::interned]
fn intern_block(&self, loc: BlockLoc) -> BlockId;
}
#[query_group::query_group]
@@ -2,6 +2,7 @@
//! representation.
mod asm;
mod format_args;
mod generics;
mod path;
@@ -19,7 +20,7 @@
use rustc_hash::FxHashMap;
use stdx::never;
use syntax::{
AstNode, AstPtr, AstToken as _, SyntaxNodePtr,
AstNode, AstPtr, SyntaxNodePtr,
ast::{
self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasGenericArgs,
HasGenericParams, HasLoopBody, HasName, HasTypeBounds, IsString, RangeItem,
@@ -31,10 +32,9 @@
use tt::TextRange;
use crate::{
AdtId, BlockId, BlockIdLt, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId,
AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId,
ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
attrs::AttrFlags,
builtin_type::BuiltinUint,
db::DefDatabase,
expr_store::{
Body, BodySourceMap, ExprPtr, ExpressionStore, ExpressionStoreBuilder,
@@ -47,13 +47,7 @@
hir::{
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId,
RecordFieldPat, RecordLitField, Statement,
format_args::{
self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind,
FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions,
FormatPlaceholder, FormatSign, FormatTrait,
},
generics::GenericParams,
RecordFieldPat, RecordLitField, Statement, generics::GenericParams,
},
item_scope::BuiltinShadowMode,
item_tree::FieldsShape,
@@ -434,10 +428,12 @@ pub struct ExprCollector<'db> {
current_try_block_label: Option<LabelId>,
label_ribs: Vec<LabelRib>,
current_binding_owner: Option<ExprId>,
unowned_bindings: Vec<BindingId>,
awaitable_context: Option<Awaitable>,
krate: base_db::Crate,
name_generator_index: usize,
}
#[derive(Clone, Debug)]
@@ -538,14 +534,21 @@ pub fn new(
current_try_block_label: None,
is_lowering_coroutine: false,
label_ribs: Vec::new(),
current_binding_owner: None,
unowned_bindings: Vec::new(),
awaitable_context: None,
current_block_legacy_macro_defs_count: FxHashMap::default(),
outer_impl_trait: false,
krate,
name_generator_index: 0,
}
}
fn generate_new_name(&mut self) -> Name {
let index = self.name_generator_index;
self.name_generator_index += 1;
Name::generate_new_name(index)
}
#[inline]
pub(crate) fn lang_items(&self) -> &'db LangItems {
self.lang_items.get_or_init(|| crate::lang_item::lang_items(self.db, self.def_map.krate()))
@@ -949,7 +952,8 @@ fn lower_type_bound(
node: ast::TypeBound,
impl_trait_lower_fn: ImplTraitLowerFn<'_>,
) -> TypeBound {
match node.kind() {
let Some(kind) = node.kind() else { return TypeBound::Error };
match kind {
ast::TypeBoundKind::PathType(binder, path_type) => {
let binder = match binder.and_then(|it| it.generic_param_list()) {
Some(gpl) => gpl
@@ -1065,12 +1069,10 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
Some(ast::BlockModifier::Const(_)) => {
self.with_label_rib(RibKind::Constant, |this| {
this.with_awaitable_block(Awaitable::No("constant block"), |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
this.store.exprs[result_expr_id] = Expr::Const(inner_expr);
this.current_binding_owner = prev_binding_owner;
result_expr_id
this.with_binding_owner(|this| {
let inner_expr = this.collect_block(e);
this.alloc_expr(Expr::Const(inner_expr), syntax_ptr)
})
})
})
}
@@ -1281,64 +1283,65 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
}
}
ast::Expr::ClosureExpr(e) => self.with_label_rib(RibKind::Closure, |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let mut args = Vec::new();
let mut arg_types = Vec::new();
if let Some(pl) = e.param_list() {
let num_params = pl.params().count();
args.reserve_exact(num_params);
arg_types.reserve_exact(num_params);
for param in pl.params() {
let pat = this.collect_pat_top(param.pat());
let type_ref =
param.ty().map(|it| this.lower_type_ref_disallow_impl_trait(it));
args.push(pat);
arg_types.push(type_ref);
this.with_binding_owner(|this| {
let mut args = Vec::new();
let mut arg_types = Vec::new();
if let Some(pl) = e.param_list() {
let num_params = pl.params().count();
args.reserve_exact(num_params);
arg_types.reserve_exact(num_params);
for param in pl.params() {
let pat = this.collect_pat_top(param.pat());
let type_ref =
param.ty().map(|it| this.lower_type_ref_disallow_impl_trait(it));
args.push(pat);
arg_types.push(type_ref);
}
}
}
let ret_type = e
.ret_type()
.and_then(|r| r.ty())
.map(|it| this.lower_type_ref_disallow_impl_trait(it));
let ret_type = e
.ret_type()
.and_then(|r| r.ty())
.map(|it| this.lower_type_ref_disallow_impl_trait(it));
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
let prev_try_block_label = this.current_try_block_label.take();
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
let prev_try_block_label = this.current_try_block_label.take();
let awaitable = if e.async_token().is_some() {
Awaitable::Yes
} else {
Awaitable::No("non-async closure")
};
let body =
this.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body()));
let closure_kind = if this.is_lowering_coroutine {
let movability = if e.static_token().is_some() {
Movability::Static
let awaitable = if e.async_token().is_some() {
Awaitable::Yes
} else {
Movability::Movable
Awaitable::No("non-async closure")
};
ClosureKind::Coroutine(movability)
} else if e.async_token().is_some() {
ClosureKind::Async
} else {
ClosureKind::Closure
};
let capture_by =
if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref };
this.is_lowering_coroutine = prev_is_lowering_coroutine;
this.current_binding_owner = prev_binding_owner;
this.current_try_block_label = prev_try_block_label;
this.store.exprs[result_expr_id] = Expr::Closure {
args: args.into(),
arg_types: arg_types.into(),
ret_type,
body,
closure_kind,
capture_by,
};
result_expr_id
let body = this
.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body()));
let closure_kind = if this.is_lowering_coroutine {
let movability = if e.static_token().is_some() {
Movability::Static
} else {
Movability::Movable
};
ClosureKind::Coroutine(movability)
} else if e.async_token().is_some() {
ClosureKind::Async
} else {
ClosureKind::Closure
};
let capture_by =
if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref };
this.is_lowering_coroutine = prev_is_lowering_coroutine;
this.current_try_block_label = prev_try_block_label;
this.alloc_expr(
Expr::Closure {
args: args.into(),
arg_types: arg_types.into(),
ret_type,
body,
closure_kind,
capture_by,
},
syntax_ptr,
)
})
}),
ast::Expr::BinExpr(e) => {
let op = e.op_kind();
@@ -1374,11 +1377,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
let initializer = self.collect_expr_opt(initializer);
let repeat = self.with_label_rib(RibKind::Constant, |this| {
if let Some(repeat) = repeat {
let syntax_ptr = AstPtr::new(&repeat);
this.collect_as_a_binding_owner_bad(
|this| this.collect_expr(repeat),
syntax_ptr,
)
this.with_binding_owner(|this| this.collect_expr(repeat))
} else {
this.missing_expr()
}
@@ -1635,31 +1634,13 @@ fn collect_tuple(
}
}
fn initialize_binding_owner(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
) -> (ExprId, Option<ExprId>) {
let result_expr_id = self.alloc_expr(Expr::Missing, syntax_ptr);
let prev_binding_owner = self.current_binding_owner.take();
self.current_binding_owner = Some(result_expr_id);
(result_expr_id, prev_binding_owner)
}
/// FIXME: This function is bad. It will produce a dangling `Missing` expr which wastes memory. Currently
/// it is used only for const blocks and repeat expressions, which are also hacky and ideally should have
/// their own body. Don't add more usage for this function so that we can remove this function after
/// separating those bodies.
fn collect_as_a_binding_owner_bad(
&mut self,
job: impl FnOnce(&mut ExprCollector<'_>) -> ExprId,
syntax_ptr: AstPtr<ast::Expr>,
) -> ExprId {
let (id, prev_owner) = self.initialize_binding_owner(syntax_ptr);
let tmp = job(self);
self.store.exprs[id] = mem::replace(&mut self.store.exprs[tmp], Expr::Missing);
self.current_binding_owner = prev_owner;
id
fn with_binding_owner(&mut self, create_expr: impl FnOnce(&mut Self) -> ExprId) -> ExprId {
let prev_unowned_bindings_len = self.unowned_bindings.len();
let expr_id = create_expr(self);
for binding in self.unowned_bindings.drain(prev_unowned_bindings_len..) {
self.store.binding_owners.insert(binding, expr_id);
}
expr_id
}
/// Desugar `try { <stmts>; <expr> }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(<expr>) }`,
@@ -1667,9 +1648,8 @@ fn collect_as_a_binding_owner_bad(
/// and save the `<new_label>` to use it as a break target for desugaring of the `?` operator.
fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId {
let try_from_output = self.lang_path(self.lang_items().TryTraitFromOutput);
let label = self.alloc_label_desugared(Label {
name: Name::generate_new_name(self.store.labels.len()),
});
let label = self.generate_new_name();
let label = self.alloc_label_desugared(Label { name: label });
let old_label = self.current_try_block_label.replace(label);
let ptr = AstPtr::new(&e).upcast();
@@ -1797,7 +1777,7 @@ fn collect_for_loop(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::ForExpr) -
this.collect_expr_opt(e.loop_body().map(|it| it.into()))
}),
};
let iter_name = Name::generate_new_name(self.store.exprs.len());
let iter_name = self.generate_new_name();
let iter_expr = self.alloc_expr(Expr::Path(Path::from(iter_name.clone())), syntax_ptr);
let iter_expr_mut = self.alloc_expr(
Expr::Ref { expr: iter_expr, rawness: Rawness::Ref, mutability: Mutability::Mut },
@@ -1858,7 +1838,7 @@ fn collect_try_operator(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::TryExp
let try_branch = self.alloc_expr(try_branch.map_or(Expr::Missing, Expr::Path), syntax_ptr);
let expr = self
.alloc_expr(Expr::Call { callee: try_branch, args: Box::new([operand]) }, syntax_ptr);
let continue_name = Name::generate_new_name(self.store.bindings.len());
let continue_name = self.generate_new_name();
let continue_binding = self.alloc_binding(
continue_name.clone(),
BindingAnnotation::Unannotated,
@@ -1876,7 +1856,7 @@ fn collect_try_operator(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::TryExp
guard: None,
expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr),
};
let break_name = Name::generate_new_name(self.store.bindings.len());
let break_name = self.generate_new_name();
let break_binding =
self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated, HygieneId::ROOT);
let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None });
@@ -2114,7 +2094,7 @@ fn collect_block_(
) -> ExprId {
let block_id = self.expander.ast_id_map().ast_id_for_block(&block).map(|file_local_id| {
let ast_id = self.expander.in_file(file_local_id);
unsafe { BlockIdLt::new(self.db, ast_id, self.module).to_static() }
self.db.intern_block(BlockLoc { ast_id, module: self.module })
});
let (module, def_map) =
@@ -2371,11 +2351,7 @@ fn collect_pat(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatI
ast::Pat::ConstBlockPat(const_block_pat) => {
if let Some(block) = const_block_pat.block_expr() {
let expr_id = self.with_label_rib(RibKind::Constant, |this| {
let syntax_ptr = AstPtr::new(&block.clone().into());
this.collect_as_a_binding_owner_bad(
|this| this.collect_block(block),
syntax_ptr,
)
this.with_binding_owner(|this| this.collect_block(block))
});
Pat::ConstBlock(expr_id)
} else {
@@ -2635,7 +2611,6 @@ fn with_opt_labeled_rib<T>(
}
// endregion: labels
// region: format
fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> {
let m = match expr {
ast::Expr::MacroExpr(m) => m,
@@ -2655,676 +2630,6 @@ fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, b
Some((exp, false))
}
fn collect_format_args(
&mut self,
f: ast::FormatArgsExpr,
syntax_ptr: AstPtr<ast::Expr>,
) -> ExprId {
let mut args = FormatArgumentsCollector::default();
f.args().for_each(|arg| {
args.add(FormatArgument {
kind: match arg.name() {
Some(name) => FormatArgumentKind::Named(name.as_name()),
None => FormatArgumentKind::Normal,
},
expr: self.collect_expr_opt(arg.expr()),
});
});
let template = f.template();
let fmt_snippet = template.as_ref().and_then(|it| match it {
ast::Expr::Literal(literal) => match literal.kind() {
ast::LiteralKind::String(s) => Some(s.text().to_owned()),
_ => None,
},
_ => None,
});
let mut mappings = vec![];
let (fmt, hygiene) = match template.and_then(|template| {
self.expand_macros_to_string(template.clone()).map(|it| (it, template))
}) {
Some(((s, is_direct_literal), template)) => {
let call_ctx = self.expander.call_syntax_ctx();
let hygiene = self.hygiene_id_for(s.syntax().text_range());
let fmt = format_args::parse(
&s,
fmt_snippet,
args,
is_direct_literal,
|name, range| {
let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));
if let Some(range) = range {
self.store
.template_map
.get_or_insert_with(Default::default)
.implicit_capture_to_source
.insert(
expr_id,
self.expander.in_file((AstPtr::new(&template), range)),
);
}
if !hygiene.is_root() {
self.store.ident_hygiene.insert(expr_id.into(), hygiene);
}
expr_id
},
|name, span| {
if let Some(span) = span {
mappings.push((span, name))
}
},
call_ctx,
);
(fmt, hygiene)
}
None => (
FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: Default::default(),
},
HygieneId::ROOT,
),
};
// Create a list of all _unique_ (argument, format trait) combinations.
// E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
let mut argmap = FxIndexSet::default();
for piece in fmt.template.iter() {
let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
if let Ok(index) = placeholder.argument.index {
argmap.insert((index, ArgumentType::Format(placeholder.format_trait)));
}
}
let lit_pieces = fmt
.template
.iter()
.enumerate()
.filter_map(|(i, piece)| {
match piece {
FormatArgsPiece::Literal(s) => {
Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))))
}
&FormatArgsPiece::Placeholder(_) => {
// Inject empty string before placeholders when not already preceded by a literal piece.
if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_))
{
Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(
Symbol::empty(),
))))
} else {
None
}
}
}
})
.collect();
let lit_pieces =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces }));
let lit_pieces = self.alloc_expr_desugared(Expr::Ref {
expr: lit_pieces,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
let format_options = {
// Generate:
// &[format_spec_0, format_spec_1, format_spec_2]
let elements = fmt
.template
.iter()
.filter_map(|piece| {
let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
Some(self.make_format_spec(placeholder, &mut argmap))
})
.collect();
let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements }));
self.alloc_expr_desugared(Expr::Ref {
expr: array,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
};
// Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists
// but `format_unsafe_arg` does not
let lang_items = self.lang_items();
let fmt_args = lang_items.FormatArguments;
let fmt_unsafe_arg = lang_items.FormatUnsafeArg;
let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none();
let idx = if use_format_args_since_1_89_0 {
self.collect_format_args_impl(syntax_ptr, fmt, argmap, lit_pieces, format_options)
} else {
self.collect_format_args_before_1_89_0_impl(
syntax_ptr,
fmt,
argmap,
lit_pieces,
format_options,
)
};
self.store
.template_map
.get_or_insert_with(Default::default)
.format_args_to_captures
.insert(idx, (hygiene, mappings));
idx
}
/// `format_args!` expansion implementation for rustc versions < `1.89.0`
fn collect_format_args_before_1_89_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments;
let args = if arguments.is_empty() {
let expr = self
.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() }));
self.alloc_expr_desugared(Expr::Ref {
expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
} else {
// Generate:
// &match (&arg0, &arg1, &…) {
// args => [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ]
// }
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let arg = self.alloc_expr_desugared(Expr::Ref {
expr: arguments[arg_index].expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.make_argument(arg, ty)
})
.collect();
let array =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
self.alloc_expr_desugared(Expr::Ref {
expr: array,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
};
// Generate:
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// unsafe { ::core::fmt::UnsafeArg::new() }
// )
let lang_items = self.lang_items();
let new_v1_formatted = self.ty_rel_lang_path(
lang_items.FormatArguments,
Name::new_symbol_root(sym::new_v1_formatted),
);
let unsafe_arg_new =
self.ty_rel_lang_path(lang_items.FormatUnsafeArg, Name::new_symbol_root(sym::new));
let new_v1_formatted =
self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path));
let unsafe_arg_new =
self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path));
let unsafe_arg_new =
self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() });
let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
id: None,
statements: Box::new([]),
tail: Some(unsafe_arg_new),
});
if !fmt.orphans.is_empty() {
unsafe_arg_new = self.alloc_expr_desugared(Expr::Block {
id: None,
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree. We cannot store them in the `Unsafe`
// block because then unsafe blocks within them will get a false "unused unsafe"
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
statements: fmt
.orphans
.into_iter()
.map(|expr| Statement::Expr { expr, has_semi: true })
.collect(),
tail: Some(unsafe_arg_new),
label: None,
});
}
self.alloc_expr(
Expr::Call {
callee: new_v1_formatted,
args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
},
syntax_ptr,
)
}
/// `format_args!` expansion implementation for rustc versions >= `1.89.0`,
/// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)
fn collect_format_args_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments;
let (let_stmts, args) = if arguments.is_empty() {
(
// Generate:
// []
vec![],
self.alloc_expr_desugared(Expr::Array(Array::ElementList {
elements: Box::default(),
})),
)
} else if argmap.len() == 1 && arguments.len() == 1 {
// Only one argument, so we don't need to make the `args` tuple.
//
// Generate:
// super let args = [<core::fmt::Arguments>::new_display(&arg)];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let ref_arg = self.alloc_expr_desugared(Expr::Ref {
expr: arguments[arg_index].expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.make_argument(ref_arg, ty)
})
.collect();
let args =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_name = Name::new_symbol_root(sym::args);
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
// TODO: We don't have `super let` yet.
let let_stmt = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args),
else_branch: None,
};
(vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into())))
} else {
// Generate:
// super let args = (&arg0, &arg1, &...);
let args_name = Name::new_symbol_root(sym::args);
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let elements = arguments
.iter()
.map(|arg| {
self.alloc_expr_desugared(Expr::Ref {
expr: arg.expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
})
.collect();
let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
// TODO: We don't have `super let` yet
let let_stmt1 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args_tuple),
else_branch: None,
};
// Generate:
// super let args = [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let args_ident_expr =
self.alloc_expr_desugared(Expr::Path(args_name.clone().into()));
let arg = self.alloc_expr_desugared(Expr::Field {
expr: args_ident_expr,
name: Name::new_tuple_field(arg_index),
});
self.make_argument(arg, ty)
})
.collect();
let array =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let let_stmt2 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(array),
else_branch: None,
};
(vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into())))
};
// Generate:
// &args
let args = self.alloc_expr_desugared(Expr::Ref {
expr: args,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
let call_block = {
// Generate:
// unsafe {
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// )
// }
let new_v1_formatted = self.ty_rel_lang_path(
self.lang_items().FormatArguments,
Name::new_symbol_root(sym::new_v1_formatted),
);
let new_v1_formatted =
self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path));
let args = [lit_pieces, args, format_options];
let call = self
.alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });
Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }
};
if !let_stmts.is_empty() {
// Generate:
// {
// super let …
// super let …
// <core::fmt::Arguments>::new_…(…)
// }
let call = self.alloc_expr_desugared(call_block);
self.alloc_expr(
Expr::Block {
id: None,
statements: let_stmts.into(),
tail: Some(call),
label: None,
},
syntax_ptr,
)
} else {
self.alloc_expr(call_block, syntax_ptr)
}
}
/// Generate a hir expression for a format_args placeholder specification.
///
/// Generates
///
/// ```text
/// <core::fmt::rt::Placeholder::new(
/// …usize, // position
/// '…', // fill
/// <core::fmt::rt::Alignment>::…, // alignment
/// …u32, // flags
/// <core::fmt::rt::Count::…>, // width
/// <core::fmt::rt::Count::…>, // precision
/// )
/// ```
fn make_format_spec(
&mut self,
placeholder: &FormatPlaceholder,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
) -> ExprId {
let lang_items = self.lang_items();
let position = match placeholder.argument.index {
Ok(arg_index) => {
let (i, _) =
argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)))
}
Err(_) => self.missing_expr(),
};
let &FormatOptions {
ref width,
ref precision,
alignment,
fill,
sign,
alternate,
zero_pad,
debug_hex,
} = &placeholder.format_options;
let precision_expr = self.make_count(precision, argmap);
let width_expr = self.make_count(width, argmap);
if self.krate.workspace_data(self.db).is_atleast_187() {
// These need to match the constants in library/core/src/fmt/rt.rs.
let align = match alignment {
Some(FormatAlignment::Left) => 0,
Some(FormatAlignment::Right) => 1,
Some(FormatAlignment::Center) => 2,
None => 3,
};
// This needs to match `Flag` in library/core/src/fmt/rt.rs.
let flags = fill.unwrap_or(' ') as u32
| ((sign == Some(FormatSign::Plus)) as u32) << 21
| ((sign == Some(FormatSign::Minus)) as u32) << 22
| (alternate as u32) << 23
| (zero_pad as u32) << 24
| ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
| ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
| (width.is_some() as u32) << 27
| (precision.is_some() as u32) << 28
| align << 29
| 1 << 31; // Highest bit always set.
let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
flags as u128,
Some(BuiltinUint::U32),
)));
let position =
RecordLitField { name: Name::new_symbol_root(sym::position), expr: position };
let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags };
let precision = RecordLitField {
name: Name::new_symbol_root(sym::precision),
expr: precision_expr,
};
let width =
RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr };
self.alloc_expr_desugared(Expr::RecordLit {
path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new),
fields: Box::new([position, flags, precision, width]),
spread: None,
})
} else {
let format_placeholder_new = {
let format_placeholder_new = self.ty_rel_lang_path(
lang_items.FormatPlaceholder,
Name::new_symbol_root(sym::new),
);
match format_placeholder_new {
Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
None => self.missing_expr(),
}
};
// This needs to match `Flag` in library/core/src/fmt/rt.rs.
let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
| (((sign == Some(FormatSign::Minus)) as u32) << 1)
| ((alternate as u32) << 2)
| ((zero_pad as u32) << 3)
| (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)
| (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);
let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
flags as u128,
Some(BuiltinUint::U32),
)));
let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));
let align = {
let align = self.ty_rel_lang_path(
lang_items.FormatAlignment,
match alignment {
Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left),
Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right),
Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center),
None => Name::new_symbol_root(sym::Unknown),
},
);
match align {
Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
None => self.missing_expr(),
}
};
self.alloc_expr_desugared(Expr::Call {
callee: format_placeholder_new,
args: Box::new([position, fill, align, flags, precision_expr, width_expr]),
})
}
}
/// Generate a hir expression for a format_args Count.
///
/// Generates:
///
/// ```text
/// <core::fmt::rt::Count>::Is(…)
/// ```
///
/// or
///
/// ```text
/// <core::fmt::rt::Count>::Param(…)
/// ```
///
/// or
///
/// ```text
/// <core::fmt::rt::Count>::Implied
/// ```
fn make_count(
&mut self,
count: &Option<FormatCount>,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
) -> ExprId {
let lang_items = self.lang_items();
match count {
Some(FormatCount::Literal(n)) => {
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
*n as u128,
// FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88
None,
)));
let count_is = match self
.ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Is))
{
Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)),
None => self.missing_expr(),
};
self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) })
}
Some(FormatCount::Argument(arg)) => {
if let Ok(arg_index) = arg.index {
let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)));
let count_param = match self
.ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Param))
{
Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
None => self.missing_expr(),
};
self.alloc_expr_desugared(Expr::Call {
callee: count_param,
args: Box::new([args]),
})
} else {
// FIXME: This drops arg causing it to potentially not be resolved/type checked
// when typing?
self.missing_expr()
}
}
None => match self
.ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Implied))
{
Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
None => self.missing_expr(),
},
}
}
/// Generate a hir expression representing an argument to a format_args invocation.
///
/// Generates:
///
/// ```text
/// <core::fmt::Argument>::new_…(arg)
/// ```
fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId {
use ArgumentType::*;
use FormatTrait::*;
let new_fn = match self.ty_rel_lang_path(
self.lang_items().FormatArgument,
Name::new_symbol_root(match ty {
Format(Display) => sym::new_display,
Format(Debug) => sym::new_debug,
Format(LowerExp) => sym::new_lower_exp,
Format(UpperExp) => sym::new_upper_exp,
Format(Octal) => sym::new_octal,
Format(Pointer) => sym::new_pointer,
Format(Binary) => sym::new_binary,
Format(LowerHex) => sym::new_lower_hex,
Format(UpperHex) => sym::new_upper_hex,
Usize => sym::from_usize,
}),
) {
Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)),
None => self.missing_expr(),
};
self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) })
}
// endregion: format
fn lang_path(&self, lang: Option<impl Into<LangItemTarget>>) -> Option<Path> {
Some(Path::LangItem(lang?.into(), None))
}
@@ -3332,9 +2637,17 @@ fn lang_path(&self, lang: Option<impl Into<LangItemTarget>>) -> Option<Path> {
fn ty_rel_lang_path(
&self,
lang: Option<impl Into<LangItemTarget>>,
relative_name: Name,
relative_name: Symbol,
) -> Option<Path> {
Some(Path::LangItem(lang?.into(), Some(relative_name)))
Some(Path::LangItem(lang?.into(), Some(Name::new_symbol_root(relative_name))))
}
fn ty_rel_lang_path_expr(
&self,
lang: Option<impl Into<LangItemTarget>>,
relative_name: Symbol,
) -> Expr {
self.ty_rel_lang_path(lang, relative_name).map_or(Expr::Missing, Expr::Path)
}
}
@@ -3379,9 +2692,7 @@ fn alloc_binding(
hygiene: HygieneId,
) -> BindingId {
let binding = self.store.bindings.alloc(Binding { name, mode, problems: None, hygiene });
if let Some(owner) = self.current_binding_owner {
self.store.binding_owners.insert(binding, owner);
}
self.unowned_bindings.push(binding);
binding
}
@@ -3453,12 +2764,6 @@ fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {
.is_some_and(|it| it.kind() == syntax::T![,])
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
enum ArgumentType {
Format(FormatTrait),
Usize,
}
/// This function find the AST fragment that corresponds to an `AssociatedTypeBinding` in the HIR.
pub fn hir_assoc_type_binding_to_ast(
segment_args: &ast::GenericArgList,
@@ -0,0 +1,1012 @@
//! Lowering of `format_args!()`.
use base_db::FxIndexSet;
use hir_expand::name::{AsName, Name};
use intern::{Symbol, sym};
use syntax::{
AstPtr, AstToken as _,
ast::{self, HasName},
};
use crate::{
builtin_type::BuiltinUint,
expr_store::{HygieneId, lower::ExprCollector, path::Path},
hir::{
Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, Statement,
format_args::{
self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind,
FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions,
FormatPlaceholder, FormatSign, FormatTrait,
},
},
lang_item::LangItemTarget,
type_ref::{Mutability, Rawness},
};
impl<'db> ExprCollector<'db> {
pub(super) fn collect_format_args(
&mut self,
f: ast::FormatArgsExpr,
syntax_ptr: AstPtr<ast::Expr>,
) -> ExprId {
let mut args = FormatArgumentsCollector::default();
f.args().for_each(|arg| {
args.add(FormatArgument {
kind: match arg.name() {
Some(name) => FormatArgumentKind::Named(name.as_name()),
None => FormatArgumentKind::Normal,
},
expr: self.collect_expr_opt(arg.expr()),
});
});
let template = f.template();
let fmt_snippet = template.as_ref().and_then(|it| match it {
ast::Expr::Literal(literal) => match literal.kind() {
ast::LiteralKind::String(s) => Some(s.text().to_owned()),
_ => None,
},
_ => None,
});
let mut mappings = vec![];
let (fmt, hygiene) = match template.and_then(|template| {
self.expand_macros_to_string(template.clone()).map(|it| (it, template))
}) {
Some(((s, is_direct_literal), template)) => {
let call_ctx = self.expander.call_syntax_ctx();
let hygiene = self.hygiene_id_for(s.syntax().text_range());
let fmt = format_args::parse(
&s,
fmt_snippet,
args,
is_direct_literal,
|name, range| {
let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));
if let Some(range) = range {
self.store
.template_map
.get_or_insert_with(Default::default)
.implicit_capture_to_source
.insert(
expr_id,
self.expander.in_file((AstPtr::new(&template), range)),
);
}
if !hygiene.is_root() {
self.store.ident_hygiene.insert(expr_id.into(), hygiene);
}
expr_id
},
|name, span| {
if let Some(span) = span {
mappings.push((span, name))
}
},
call_ctx,
);
(fmt, hygiene)
}
None => (
FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: Default::default(),
},
HygieneId::ROOT,
),
};
let idx = if self.lang_items().FormatCount.is_none() {
self.collect_format_args_after_1_93_0_impl(syntax_ptr, fmt)
} else {
self.collect_format_args_before_1_93_0_impl(syntax_ptr, fmt)
};
self.store
.template_map
.get_or_insert_with(Default::default)
.format_args_to_captures
.insert(idx, (hygiene, mappings));
idx
}
fn collect_format_args_after_1_93_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
) -> ExprId {
let lang_items = self.lang_items();
// Create a list of all _unique_ (argument, format trait) combinations.
// E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
//
// We use usize::MAX for arguments that don't exist, because that can never be a valid index
// into the arguments array.
let mut argmap = FxIndexSet::default();
let mut incomplete_lit = String::new();
let mut implicit_arg_index = 0;
let mut bytecode = Vec::new();
let template = if fmt.template.is_empty() {
// Treat empty templates as a single literal piece (with an empty string),
// so we produce `from_str("")` for those.
&[FormatArgsPiece::Literal(sym::__empty)][..]
} else {
&fmt.template[..]
};
// See library/core/src/fmt/mod.rs for the format string encoding format.
for (i, piece) in template.iter().enumerate() {
match piece {
FormatArgsPiece::Literal(sym) => {
// Coalesce adjacent literal pieces.
if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) {
incomplete_lit.push_str(sym.as_str());
continue;
}
let mut s = if incomplete_lit.is_empty() {
sym.as_str()
} else {
incomplete_lit.push_str(sym.as_str());
&incomplete_lit
};
// If this is the last piece and was the only piece, that means
// there are no placeholders and the entire format string is just a literal.
//
// In that case, we can just use `from_str`.
if i + 1 == template.len() && bytecode.is_empty() {
// Generate:
// <core::fmt::Arguments>::from_str("meow")
let from_str = self.ty_rel_lang_path_desugared_expr(
lang_items.FormatArguments,
sym::from_str,
);
let sym =
if incomplete_lit.is_empty() { sym.clone() } else { Symbol::intern(s) };
let s = self.alloc_expr_desugared(Expr::Literal(Literal::String(sym)));
let from_str = self.alloc_expr(
Expr::Call { callee: from_str, args: Box::new([s]) },
syntax_ptr,
);
return if !fmt.arguments.arguments.is_empty() {
// With an incomplete format string (e.g. only an opening `{`), it's possible for `arguments`
// to be non-empty when reaching this code path.
self.alloc_expr(
Expr::Block {
id: None,
statements: fmt
.arguments
.arguments
.iter()
.map(|arg| Statement::Expr {
expr: arg.expr,
has_semi: true,
})
.collect(),
tail: Some(from_str),
label: None,
},
syntax_ptr,
)
} else {
from_str
};
}
// Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries.
while !s.is_empty() {
let len = s.floor_char_boundary(usize::from(u16::MAX));
if len < 0x80 {
bytecode.push(len as u8);
} else {
bytecode.push(0x80);
bytecode.extend_from_slice(&(len as u16).to_le_bytes());
}
bytecode.extend(&s.as_bytes()[..len]);
s = &s[len..];
}
incomplete_lit.clear();
}
FormatArgsPiece::Placeholder(p) => {
// Push the start byte and remember its index so we can set the option bits later.
let i = bytecode.len();
bytecode.push(0xC0);
let position = match &p.argument.index {
&Ok(it) => it,
Err(_) => usize::MAX,
};
let position = argmap
.insert_full((position, ArgumentType::Format(p.format_trait)))
.0 as u64;
// This needs to match the constants in library/core/src/fmt/mod.rs.
let o = &p.format_options;
let align = match o.alignment {
Some(FormatAlignment::Left) => 0,
Some(FormatAlignment::Right) => 1,
Some(FormatAlignment::Center) => 2,
None => 3,
};
let default_flags = 0x6000_0020;
let flags: u32 = o.fill.unwrap_or(' ') as u32
| ((o.sign == Some(FormatSign::Plus)) as u32) << 21
| ((o.sign == Some(FormatSign::Minus)) as u32) << 22
| (o.alternate as u32) << 23
| (o.zero_pad as u32) << 24
| ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
| ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
| (o.width.is_some() as u32) << 27
| (o.precision.is_some() as u32) << 28
| align << 29;
if flags != default_flags {
bytecode[i] |= 1;
bytecode.extend_from_slice(&flags.to_le_bytes());
if let Some(val) = &o.width {
let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);
// Only encode if nonzero; zero is the default.
if indirect || val != 0 {
bytecode[i] |= 1 << 1 | (indirect as u8) << 4;
bytecode.extend_from_slice(&val.to_le_bytes());
}
}
if let Some(val) = &o.precision {
let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);
// Only encode if nonzero; zero is the default.
if indirect || val != 0 {
bytecode[i] |= 1 << 2 | (indirect as u8) << 5;
bytecode.extend_from_slice(&val.to_le_bytes());
}
}
}
if implicit_arg_index != position {
bytecode[i] |= 1 << 3;
bytecode.extend_from_slice(&(position as u16).to_le_bytes());
}
implicit_arg_index = position + 1;
}
}
}
assert!(incomplete_lit.is_empty());
// Zero terminator.
bytecode.push(0);
// Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before.
if argmap.len() > u16::MAX as usize {
// FIXME: Emit an error.
// ctx.dcx().span_err(macsp, "too many format arguments");
}
let arguments = &fmt.arguments.arguments[..];
let (mut statements, args) = if arguments.is_empty() {
// Generate:
// []
(
Vec::new(),
self.alloc_expr_desugared(Expr::Array(Array::ElementList {
elements: Box::new([]),
})),
)
} else {
// Generate:
// super let args = (&arg0, &arg1, &…);
let args_name = self.generate_new_name();
let args_path = Path::from(args_name.clone());
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let elements = arguments
.iter()
.map(|arg| {
self.alloc_expr_desugared(Expr::Ref {
expr: arg.expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
})
.collect();
let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
// FIXME: Make this a `super let` when we have this statement.
let let_statement_1 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args_tuple),
else_branch: None,
};
// Generate:
// super let args = [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let args_ident_expr = self.alloc_expr_desugared(Expr::Path(args_path.clone()));
let arg = self.alloc_expr_desugared(Expr::Field {
expr: args_ident_expr,
name: Name::new_tuple_field(arg_index),
});
self.make_argument(arg, ty)
})
.collect();
let args =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_binding =
self.alloc_binding(args_name, BindingAnnotation::Unannotated, HygieneId::ROOT);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
// FIXME: Make this a `super let` when we have this statement.
let let_statement_2 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args),
else_branch: None,
};
(
vec![let_statement_1, let_statement_2],
self.alloc_expr_desugared(Expr::Path(args_path)),
)
};
// Generate:
// unsafe {
// <core::fmt::Arguments>::new(b"…", &args)
// }
let template = self
.alloc_expr_desugared(Expr::Literal(Literal::ByteString(bytecode.into_boxed_slice())));
let call = {
let new = self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new);
let args = self.alloc_expr_desugared(Expr::Ref {
expr: args,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) })
};
let call = self.alloc_expr(
Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) },
syntax_ptr,
);
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree. We cannot store them in the `Unsafe`
// block because then unsafe blocks within them will get a false "unused unsafe"
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
statements
.extend(fmt.orphans.into_iter().map(|expr| Statement::Expr { expr, has_semi: true }));
if !statements.is_empty() {
// Generate:
// {
// super let …
// super let …
// <core::fmt::Arguments>::new(…)
// }
self.alloc_expr(
Expr::Block {
id: None,
statements: statements.into_boxed_slice(),
tail: Some(call),
label: None,
},
syntax_ptr,
)
} else {
call
}
}
/// Get the value for a `width` or `precision` field.
///
/// Returns the value and whether it is indirect (an indexed argument) or not.
fn make_count_after_1_93_0(
&self,
count: &FormatCount,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
) -> (bool, u16) {
match count {
FormatCount::Literal(n) => (false, *n),
FormatCount::Argument(arg) => {
let index = match &arg.index {
&Ok(it) => it,
Err(_) => usize::MAX,
};
(true, argmap.insert_full((index, ArgumentType::Usize)).0 as u16)
}
}
}
fn collect_format_args_before_1_93_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
) -> ExprId {
// Create a list of all _unique_ (argument, format trait) combinations.
// E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
let mut argmap = FxIndexSet::default();
for piece in fmt.template.iter() {
let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
if let Ok(index) = placeholder.argument.index {
argmap.insert((index, ArgumentType::Format(placeholder.format_trait)));
}
}
let lit_pieces = fmt
.template
.iter()
.enumerate()
.filter_map(|(i, piece)| {
match piece {
FormatArgsPiece::Literal(s) => {
Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))))
}
&FormatArgsPiece::Placeholder(_) => {
// Inject empty string before placeholders when not already preceded by a literal piece.
if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_))
{
Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(
Symbol::empty(),
))))
} else {
None
}
}
}
})
.collect();
let lit_pieces =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces }));
let lit_pieces = self.alloc_expr_desugared(Expr::Ref {
expr: lit_pieces,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
let format_options = {
// Generate:
// &[format_spec_0, format_spec_1, format_spec_2]
let elements = fmt
.template
.iter()
.filter_map(|piece| {
let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
Some(self.make_format_spec(placeholder, &mut argmap))
})
.collect();
let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements }));
self.alloc_expr_desugared(Expr::Ref {
expr: array,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
};
// Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists
// but `format_unsafe_arg` does not
let lang_items = self.lang_items();
let fmt_args = lang_items.FormatArguments;
let fmt_unsafe_arg = lang_items.FormatUnsafeArg;
let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none();
if use_format_args_since_1_89_0 {
self.collect_format_args_after_1_89_0_impl(
syntax_ptr,
fmt,
argmap,
lit_pieces,
format_options,
)
} else {
self.collect_format_args_before_1_89_0_impl(
syntax_ptr,
fmt,
argmap,
lit_pieces,
format_options,
)
}
}
/// `format_args!` expansion implementation for rustc versions < `1.89.0`
fn collect_format_args_before_1_89_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments;
let args = if arguments.is_empty() {
let expr = self
.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() }));
self.alloc_expr_desugared(Expr::Ref {
expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
} else {
// Generate:
// &match (&arg0, &arg1, &…) {
// args => [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ]
// }
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let arg = self.alloc_expr_desugared(Expr::Ref {
expr: arguments[arg_index].expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.make_argument(arg, ty)
})
.collect();
let array =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
self.alloc_expr_desugared(Expr::Ref {
expr: array,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
};
// Generate:
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// unsafe { ::core::fmt::UnsafeArg::new() }
// )
let lang_items = self.lang_items();
let new_v1_formatted =
self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted);
let unsafe_arg_new =
self.ty_rel_lang_path_desugared_expr(lang_items.FormatUnsafeArg, sym::new);
let unsafe_arg_new =
self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() });
let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
id: None,
statements: Box::new([]),
tail: Some(unsafe_arg_new),
});
if !fmt.orphans.is_empty() {
unsafe_arg_new = self.alloc_expr_desugared(Expr::Block {
id: None,
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree. We cannot store them in the `Unsafe`
// block because then unsafe blocks within them will get a false "unused unsafe"
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
statements: fmt
.orphans
.into_iter()
.map(|expr| Statement::Expr { expr, has_semi: true })
.collect(),
tail: Some(unsafe_arg_new),
label: None,
});
}
self.alloc_expr(
Expr::Call {
callee: new_v1_formatted,
args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
},
syntax_ptr,
)
}
/// `format_args!` expansion implementation for rustc versions >= `1.89.0`,
/// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)
fn collect_format_args_after_1_89_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments;
let (let_stmts, args) = if arguments.is_empty() {
(
// Generate:
// []
vec![],
self.alloc_expr_desugared(Expr::Array(Array::ElementList {
elements: Box::default(),
})),
)
} else if argmap.len() == 1 && arguments.len() == 1 {
// Only one argument, so we don't need to make the `args` tuple.
//
// Generate:
// super let args = [<core::fmt::Arguments>::new_display(&arg)];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let ref_arg = self.alloc_expr_desugared(Expr::Ref {
expr: arguments[arg_index].expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.make_argument(ref_arg, ty)
})
.collect();
let args =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_name = self.generate_new_name();
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
// TODO: We don't have `super let` yet.
let let_stmt = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args),
else_branch: None,
};
(vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into())))
} else {
// Generate:
// super let args = (&arg0, &arg1, &...);
let args_name = self.generate_new_name();
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let elements = arguments
.iter()
.map(|arg| {
self.alloc_expr_desugared(Expr::Ref {
expr: arg.expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
})
.collect();
let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
// TODO: We don't have `super let` yet
let let_stmt1 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args_tuple),
else_branch: None,
};
// Generate:
// super let args = [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let args_ident_expr =
self.alloc_expr_desugared(Expr::Path(args_name.clone().into()));
let arg = self.alloc_expr_desugared(Expr::Field {
expr: args_ident_expr,
name: Name::new_tuple_field(arg_index),
});
self.make_argument(arg, ty)
})
.collect();
let array =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_binding = self.alloc_binding(
args_name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let let_stmt2 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(array),
else_branch: None,
};
(vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into())))
};
// Generate:
// &args
let args = self.alloc_expr_desugared(Expr::Ref {
expr: args,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
let call_block = {
// Generate:
// unsafe {
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// )
// }
let new_v1_formatted = self.ty_rel_lang_path_desugared_expr(
self.lang_items().FormatArguments,
sym::new_v1_formatted,
);
let args = [lit_pieces, args, format_options];
let call = self
.alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });
Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }
};
if !let_stmts.is_empty() {
// Generate:
// {
// super let …
// super let …
// <core::fmt::Arguments>::new_…(…)
// }
let call = self.alloc_expr_desugared(call_block);
self.alloc_expr(
Expr::Block {
id: None,
statements: let_stmts.into(),
tail: Some(call),
label: None,
},
syntax_ptr,
)
} else {
self.alloc_expr(call_block, syntax_ptr)
}
}
/// Generate a hir expression for a format_args placeholder specification.
///
/// Generates
///
/// ```text
/// <core::fmt::rt::Placeholder::new(
/// …usize, // position
/// '…', // fill
/// <core::fmt::rt::Alignment>::…, // alignment
/// …u32, // flags
/// <core::fmt::rt::Count::…>, // width
/// <core::fmt::rt::Count::…>, // precision
/// )
/// ```
fn make_format_spec(
&mut self,
placeholder: &FormatPlaceholder,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
) -> ExprId {
let lang_items = self.lang_items();
let position = match placeholder.argument.index {
Ok(arg_index) => {
let (i, _) =
argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)))
}
Err(_) => self.missing_expr(),
};
let &FormatOptions {
ref width,
ref precision,
alignment,
fill,
sign,
alternate,
zero_pad,
debug_hex,
} = &placeholder.format_options;
let precision_expr = self.make_count_before_1_93_0(precision, argmap);
let width_expr = self.make_count_before_1_93_0(width, argmap);
if self.krate.workspace_data(self.db).is_atleast_187() {
// These need to match the constants in library/core/src/fmt/rt.rs.
let align = match alignment {
Some(FormatAlignment::Left) => 0,
Some(FormatAlignment::Right) => 1,
Some(FormatAlignment::Center) => 2,
None => 3,
};
// This needs to match `Flag` in library/core/src/fmt/rt.rs.
let flags = fill.unwrap_or(' ') as u32
| ((sign == Some(FormatSign::Plus)) as u32) << 21
| ((sign == Some(FormatSign::Minus)) as u32) << 22
| (alternate as u32) << 23
| (zero_pad as u32) << 24
| ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
| ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
| (width.is_some() as u32) << 27
| (precision.is_some() as u32) << 28
| align << 29
| 1 << 31; // Highest bit always set.
let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
flags as u128,
Some(BuiltinUint::U32),
)));
let position =
RecordLitField { name: Name::new_symbol_root(sym::position), expr: position };
let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags };
let precision = RecordLitField {
name: Name::new_symbol_root(sym::precision),
expr: precision_expr,
};
let width =
RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr };
self.alloc_expr_desugared(Expr::RecordLit {
path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new),
fields: Box::new([position, flags, precision, width]),
spread: None,
})
} else {
let format_placeholder_new =
self.ty_rel_lang_path_desugared_expr(lang_items.FormatPlaceholder, sym::new);
// This needs to match `Flag` in library/core/src/fmt/rt.rs.
let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
| (((sign == Some(FormatSign::Minus)) as u32) << 1)
| ((alternate as u32) << 2)
| ((zero_pad as u32) << 3)
| (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)
| (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);
let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
flags as u128,
Some(BuiltinUint::U32),
)));
let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));
let align = self.ty_rel_lang_path_desugared_expr(
lang_items.FormatAlignment,
match alignment {
Some(FormatAlignment::Left) => sym::Left,
Some(FormatAlignment::Right) => sym::Right,
Some(FormatAlignment::Center) => sym::Center,
None => sym::Unknown,
},
);
self.alloc_expr_desugared(Expr::Call {
callee: format_placeholder_new,
args: Box::new([position, fill, align, flags, precision_expr, width_expr]),
})
}
}
/// Generate a hir expression for a format_args Count.
///
/// Generates:
///
/// ```text
/// <core::fmt::rt::Count>::Is(…)
/// ```
///
/// or
///
/// ```text
/// <core::fmt::rt::Count>::Param(…)
/// ```
///
/// or
///
/// ```text
/// <core::fmt::rt::Count>::Implied
/// ```
fn make_count_before_1_93_0(
&mut self,
count: &Option<FormatCount>,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
) -> ExprId {
let lang_items = self.lang_items();
match count {
Some(FormatCount::Literal(n)) => {
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
*n as u128,
// FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88
None,
)));
let count_is =
self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Is);
self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) })
}
Some(FormatCount::Argument(arg)) => {
if let Ok(arg_index) = arg.index {
let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)));
let count_param =
self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Param);
self.alloc_expr_desugared(Expr::Call {
callee: count_param,
args: Box::new([args]),
})
} else {
// FIXME: This drops arg causing it to potentially not be resolved/type checked
// when typing?
self.missing_expr()
}
}
None => match self.ty_rel_lang_path(lang_items.FormatCount, sym::Implied) {
Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
None => self.missing_expr(),
},
}
}
/// Generate a hir expression representing an argument to a format_args invocation.
///
/// Generates:
///
/// ```text
/// <core::fmt::Argument>::new_…(arg)
/// ```
fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId {
use ArgumentType::*;
use FormatTrait::*;
let new_fn = self.ty_rel_lang_path_desugared_expr(
self.lang_items().FormatArgument,
match ty {
Format(Display) => sym::new_display,
Format(Debug) => sym::new_debug,
Format(LowerExp) => sym::new_lower_exp,
Format(UpperExp) => sym::new_upper_exp,
Format(Octal) => sym::new_octal,
Format(Pointer) => sym::new_pointer,
Format(Binary) => sym::new_binary,
Format(LowerHex) => sym::new_lower_hex,
Format(UpperHex) => sym::new_upper_hex,
Usize => sym::from_usize,
},
);
self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) })
}
fn ty_rel_lang_path_desugared_expr(
&mut self,
lang: Option<impl Into<LangItemTarget>>,
relative_name: Symbol,
) -> ExprId {
self.alloc_expr_desugared(self.ty_rel_lang_path_expr(lang, relative_name))
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
enum ArgumentType {
Format(FormatTrait),
Usize,
}
@@ -161,9 +161,9 @@ fn main() {
match builtin#lang(into_iter)(
0..10,
) {
mut <ra@gennew>11 => loop {
mut <ra@gennew>0 => loop {
match builtin#lang(next)(
&mut <ra@gennew>11,
&mut <ra@gennew>0,
) {
builtin#lang(None) => break,
builtin#lang(Some)(ident) => {
@@ -261,10 +261,10 @@ fn main() {
}
#[test]
fn desugar_builtin_format_args() {
fn desugar_builtin_format_args_before_1_93_0() {
let (db, body, def) = lower(
r#"
//- minicore: fmt
//- minicore: fmt_before_1_93_0
fn main() {
let are = "are";
let count = 10;
@@ -278,16 +278,16 @@ fn main() {
let are = "are";
let count = 10;
{
let args = (&"fancy", &(), &"!", &count, &are, );
let args = [
let <ra@gennew>0 = (&"fancy", &(), &"!", &count, &are, );
let <ra@gennew>0 = [
builtin#lang(Argument::new_display)(
args.3,
<ra@gennew>0.3,
), builtin#lang(Argument::new_display)(
args.0,
<ra@gennew>0.0,
), builtin#lang(Argument::new_debug)(
args.4,
<ra@gennew>0.4,
), builtin#lang(Argument::new_display)(
args.2,
<ra@gennew>0.2,
),
];
unsafe {
@@ -295,7 +295,7 @@ fn main() {
&[
"\u{1b}hello ", " ", " friends, we ", " ", "",
],
&args,
&<ra@gennew>0,
&[
builtin#lang(Placeholder::new)(
0usize,
@@ -343,6 +343,59 @@ fn main() {
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test]
fn desugar_builtin_format_args() {
let (db, body, def) = lower(
r#"
//- minicore: fmt
fn main() {
let are = "are";
let count = 10;
builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", orphan = (), last = "!");
builtin#format_args("hello world");
builtin#format_args("hello world", orphan = ());
}
"#,
);
expect![[r#"
fn main() {
let are = "are";
let count = 10;
{
let <ra@gennew>0 = (&"fancy", &(), &"!", &count, &are, );
let <ra@gennew>0 = [
builtin#lang(Argument::new_display)(
<ra@gennew>0.3,
), builtin#lang(Argument::new_display)(
<ra@gennew>0.0,
), builtin#lang(Argument::new_debug)(
<ra@gennew>0.4,
), builtin#lang(Argument::new_display)(
<ra@gennew>0.2,
),
];
();
unsafe {
builtin#lang(Arguments::new)(
"\x07\x1bhello \xc3 \x00\x00i\x02\x00\x01 \xc0\r friends, we \xc0\x01 \xc8\x01\x00\xc8\x03\x00\x00",
&<ra@gennew>0,
)
}
};
builtin#lang(Arguments::from_str)(
"hello world",
);
{
();
builtin#lang(Arguments::from_str)(
"hello world",
)
};
}"#]]
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test]
fn test_macro_hygiene() {
let (db, body, def) = lower(
@@ -382,27 +435,16 @@ pub(crate) fn new(message: impl Into<core::fmt::Arguments>) -> SsrError {
fn main() {
_ = ra_test_fixture::error::SsrError::new(
{
let args = [
let <ra@gennew>0 = (&node.text(), );
let <ra@gennew>0 = [
builtin#lang(Argument::new_display)(
&node.text(),
<ra@gennew>0.0,
),
];
unsafe {
builtin#lang(Arguments::new_v1_formatted)(
&[
"Failed to resolve path `", "`",
],
&args,
&[
builtin#lang(Placeholder::new)(
0usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
),
],
builtin#lang(Arguments::new)(
"\x18Failed to resolve path `\xc0\x01`\x00",
&<ra@gennew>0,
)
}
},
@@ -195,55 +195,9 @@ fn f() {
Id(1c00),
),
block: Some(
BlockIdLt {
[salsa id]: Id(3c01),
ast_id: InFileWrapper {
file_id: FileId(
EditionedFileIdData {
editioned_file_id: EditionedFileId(
0,
Edition2024,
),
krate: Crate(
Id(1c00),
),
},
),
value: FileAstId::<syntax::ast::generated::nodes::BlockExpr>(ErasedFileAstId { kind: BlockExpr, index: 0, hash: F9BF }),
},
module: ModuleIdLt {
[salsa id]: Id(3002),
krate: Crate(
Id(1c00),
),
block: Some(
BlockIdLt {
[salsa id]: Id(3c00),
ast_id: InFileWrapper {
file_id: FileId(
EditionedFileIdData {
editioned_file_id: EditionedFileId(
0,
Edition2024,
),
krate: Crate(
Id(1c00),
),
},
),
value: FileAstId::<syntax::ast::generated::nodes::BlockExpr>(ErasedFileAstId { kind: BlockExpr, index: 0, hash: C181 }),
},
module: ModuleIdLt {
[salsa id]: Id(3000),
krate: Crate(
Id(1c00),
),
block: None,
},
},
),
},
},
BlockId(
3c01,
),
),
}"#]],
);
@@ -197,3 +197,15 @@ fn allowed3<Param[0], Param[1]>(Param[1])
"#]],
);
}
#[test]
fn regression_21138() {
lower_and_print(
r#"
fn foo(v: for<'a> Trait1 + Trait2) {}
"#,
expect![[r#"
fn foo(dyn for<'a> Trait1 + Trait2) {...}
"#]],
);
}
@@ -12,7 +12,7 @@
use rustc_hash::FxHashSet;
use crate::{
FindPathConfig, ModuleDefId, ModuleIdLt,
FindPathConfig, ModuleDefId, ModuleId,
db::DefDatabase,
item_scope::ItemInNs,
nameres::DefMap,
@@ -24,7 +24,7 @@
pub fn find_path(
db: &dyn DefDatabase,
item: ItemInNs,
from: ModuleIdLt<'_>,
from: ModuleId,
mut prefix_kind: PrefixKind,
ignore_local_imports: bool,
mut cfg: FindPathConfig,
@@ -102,14 +102,14 @@ struct FindPathCtx<'db> {
cfg: FindPathConfig,
ignore_local_imports: bool,
is_std_item: bool,
from: ModuleIdLt<'db>,
from: ModuleId,
from_crate: Crate,
crate_root: ModuleIdLt<'db>,
crate_root: ModuleId,
from_def_map: &'db DefMap,
fuel: Cell<usize>,
}
/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleIdLt<'_>
/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId
fn find_path_inner(ctx: &FindPathCtx<'_>, item: ItemInNs, max_len: usize) -> Option<ModPath> {
// - if the item is a module, jump straight to module search
if !ctx.is_std_item
@@ -157,10 +157,10 @@ fn find_path_inner(ctx: &FindPathCtx<'_>, item: ItemInNs, max_len: usize) -> Opt
}
#[tracing::instrument(skip_all)]
fn find_path_for_module<'db>(
ctx: &'db FindPathCtx<'db>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
module_id: ModuleIdLt<'db>,
fn find_path_for_module(
ctx: &FindPathCtx<'_>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
module_id: ModuleId,
maybe_extern: bool,
max_len: usize,
) -> Option<Choice> {
@@ -217,7 +217,7 @@ fn find_path_for_module<'db>(
ctx.db,
ctx.from_def_map,
ctx.from,
ItemInNs::Types(unsafe { module_id.to_static() }.into()),
ItemInNs::Types(module_id.into()),
ctx.ignore_local_imports,
);
if let Some(scope_name) = scope_name {
@@ -244,7 +244,7 @@ fn find_path_for_module<'db>(
}
// - if the module is in the prelude, return it by that path
let item = ItemInNs::Types(unsafe { module_id.to_static() }.into());
let item = ItemInNs::Types(module_id.into());
if let Some(choice) = find_in_prelude(ctx.db, ctx.from_def_map, item, ctx.from) {
return Some(choice);
}
@@ -257,10 +257,10 @@ fn find_path_for_module<'db>(
best_choice
}
fn find_in_scope<'db>(
db: &'db dyn DefDatabase,
fn find_in_scope(
db: &dyn DefDatabase,
def_map: &DefMap,
from: ModuleIdLt<'db>,
from: ModuleId,
item: ItemInNs,
ignore_local_imports: bool,
) -> Option<Name> {
@@ -278,7 +278,7 @@ fn find_in_prelude(
db: &dyn DefDatabase,
local_def_map: &DefMap,
item: ItemInNs,
from: ModuleIdLt<'_>,
from: ModuleId,
) -> Option<Choice> {
let (prelude_module, _) = local_def_map.prelude()?;
let prelude_def_map = prelude_module.def_map(db);
@@ -310,8 +310,8 @@ fn find_in_prelude(
fn is_kw_kind_relative_to_from(
db: &dyn DefDatabase,
def_map: &DefMap,
item: ModuleIdLt<'_>,
from: ModuleIdLt<'_>,
item: ModuleId,
from: ModuleId,
) -> Option<PathKind> {
if item.krate(db) != from.krate(db) || item.block(db).is_some() || from.block(db).is_some() {
return None;
@@ -332,9 +332,9 @@ fn is_kw_kind_relative_to_from(
}
#[tracing::instrument(skip_all)]
fn calculate_best_path<'db>(
ctx: &'db FindPathCtx<'db>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
fn calculate_best_path(
ctx: &FindPathCtx<'_>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
item: ItemInNs,
max_len: usize,
best_choice: &mut Option<Choice>,
@@ -372,9 +372,9 @@ fn calculate_best_path<'db>(
}
}
fn find_in_sysroot<'db>(
ctx: &'db FindPathCtx<'db>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
fn find_in_sysroot(
ctx: &FindPathCtx<'_>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
item: ItemInNs,
max_len: usize,
best_choice: &mut Option<Choice>,
@@ -418,9 +418,9 @@ fn find_in_sysroot<'db>(
});
}
fn find_in_dep<'db>(
ctx: &'db FindPathCtx<'db>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
fn find_in_dep(
ctx: &FindPathCtx<'_>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
item: ItemInNs,
max_len: usize,
best_choice: &mut Option<Choice>,
@@ -461,9 +461,9 @@ fn find_in_dep<'db>(
}
}
fn calculate_best_path_local<'db>(
ctx: &'db FindPathCtx<'db>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
fn calculate_best_path_local(
ctx: &FindPathCtx<'_>,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
item: ItemInNs,
max_len: usize,
best_choice: &mut Option<Choice>,
@@ -558,11 +558,11 @@ fn path_kind_len(kind: PathKind) -> usize {
}
/// Finds locations in `from.krate` from which `item` can be imported by `from`.
fn find_local_import_locations<'db>(
ctx: &'db FindPathCtx<'db>,
fn find_local_import_locations(
ctx: &FindPathCtx<'_>,
item: ItemInNs,
visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>,
mut cb: impl FnMut(&mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, &Name, ModuleIdLt<'db>),
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
mut cb: impl FnMut(&mut FxHashSet<(ItemInNs, ModuleId)>, &Name, ModuleId),
) {
let _p = tracing::info_span!("find_local_import_locations").entered();
let db = ctx.db;
@@ -496,7 +496,7 @@ mod tests {
use expect_test::{Expect, expect};
use test_fixture::WithFixture;
use crate::{ItemContainerId, Lookup, ModuleIdLt, nameres::assoc::TraitItems, test_db::TestDB};
use crate::{ItemContainerId, Lookup, nameres::assoc::TraitItems, test_db::TestDB};
use super::*;
@@ -628,8 +628,8 @@ fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
expect.assert_eq(&actual)
}
fn render_path<'db>(db: &'db dyn DefDatabase, info: &ImportInfo) -> String {
let mut module: ModuleIdLt<'db> = info.container;
fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
let mut module = info.container;
let mut segments = vec![&info.name];
let def_map = module.def_map(db);
@@ -58,7 +58,7 @@
use thin_vec::ThinVec;
use triomphe::Arc;
use crate::{BlockId, db::DefDatabase};
use crate::{BlockId, Lookup, db::DefDatabase};
pub(crate) use crate::item_tree::{
attrs::*,
@@ -150,10 +150,10 @@ pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc
let _p = tracing::info_span!("block_item_tree_query", ?block).entered();
static EMPTY: OnceLock<Arc<ItemTree>> = OnceLock::new();
let ast_id = block.ast_id(db);
let block = ast_id.to_node(db);
let loc = block.lookup(db);
let block = loc.ast_id.to_node(db);
let ctx = lower::Ctx::new(db, ast_id.file_id);
let ctx = lower::Ctx::new(db, loc.ast_id.file_id);
let mut item_tree = ctx.lower_block(&block);
let ItemTree { top_level, top_attrs, attrs, vis, big_data, small_data } = &item_tree;
if small_data.is_empty()
@@ -420,31 +420,13 @@ pub struct ProcMacroLoc {
impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_macro);
impl_loc!(ProcMacroLoc, id: Fn, container: ModuleId);
#[salsa_macros::tracked(debug)]
#[derive(PartialOrd, Ord)]
pub struct BlockIdLt<'db> {
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct BlockLoc {
pub ast_id: AstId<ast::BlockExpr>,
/// The containing module.
pub module: ModuleIdLt<'db>,
}
pub type BlockId = BlockIdLt<'static>;
impl BlockIdLt<'_> {
/// # Safety
///
/// The caller must ensure that the `ModuleId` is not leaked outside of query computations.
pub unsafe fn to_static(self) -> BlockId {
unsafe { std::mem::transmute(self) }
}
}
impl BlockId {
/// # Safety
///
/// The caller must ensure that the `BlockId` comes from the given database.
pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> BlockIdLt<'db> {
unsafe { std::mem::transmute(self) }
}
pub module: ModuleId,
}
impl_intern!(BlockId, BlockLoc, intern_block, lookup_intern_block);
#[salsa_macros::tracked(debug)]
#[derive(PartialOrd, Ord)]
@@ -454,26 +436,34 @@ pub struct ModuleIdLt<'db> {
/// If this `ModuleId` was derived from a `DefMap` for a block expression, this stores the
/// `BlockId` of that block expression. If `None`, this module is part of the crate-level
/// `DefMap` of `krate`.
pub block: Option<BlockIdLt<'db>>,
pub block: Option<BlockId>,
}
pub type ModuleId = ModuleIdLt<'static>;
impl<'db> ModuleIdLt<'db> {
impl ModuleIdLt<'_> {
/// # Safety
///
/// The caller must ensure that the `ModuleId` is not leaked outside of query computations.
pub unsafe fn to_static(self) -> ModuleId {
unsafe { std::mem::transmute(self) }
}
}
impl ModuleId {
/// # Safety
///
/// The caller must ensure that the `ModuleId` comes from the given database.
pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> ModuleIdLt<'db> {
unsafe { std::mem::transmute(self) }
}
pub fn def_map(self, db: &'db dyn DefDatabase) -> &'db DefMap {
pub fn def_map(self, db: &dyn DefDatabase) -> &DefMap {
match self.block(db) {
Some(block) => block_def_map(db, block),
None => crate_def_map(db, self.krate(db)),
}
}
pub(crate) fn local_def_map(self, db: &'db dyn DefDatabase) -> (&'db DefMap, &'db LocalDefMap) {
pub(crate) fn local_def_map(self, db: &dyn DefDatabase) -> (&DefMap, &LocalDefMap) {
match self.block(db) {
Some(block) => (block_def_map(db, block), self.only_local_def_map(db)),
None => {
@@ -483,15 +473,15 @@ pub(crate) fn local_def_map(self, db: &'db dyn DefDatabase) -> (&'db DefMap, &'d
}
}
pub(crate) fn only_local_def_map(self, db: &'db dyn DefDatabase) -> &'db LocalDefMap {
pub(crate) fn only_local_def_map(self, db: &dyn DefDatabase) -> &LocalDefMap {
crate_local_def_map(db, self.krate(db)).local(db)
}
pub fn crate_def_map(self, db: &'db dyn DefDatabase) -> &'db DefMap {
pub fn crate_def_map(self, db: &dyn DefDatabase) -> &DefMap {
crate_def_map(db, self.krate(db))
}
pub fn name(self, db: &'db dyn DefDatabase) -> Option<Name> {
pub fn name(self, db: &dyn DefDatabase) -> Option<Name> {
let def_map = self.def_map(db);
let parent = def_map[self].parent?;
def_map[parent].children.iter().find_map(|(name, module_id)| {
@@ -501,24 +491,15 @@ pub fn name(self, db: &'db dyn DefDatabase) -> Option<Name> {
/// Returns the module containing `self`, either the parent `mod`, or the module (or block) containing
/// the block, if `self` corresponds to a block expression.
pub fn containing_module(self, db: &'db dyn DefDatabase) -> Option<ModuleIdLt<'db>> {
pub fn containing_module(self, db: &dyn DefDatabase) -> Option<ModuleId> {
self.def_map(db).containing_module(self)
}
pub fn is_block_module(self, db: &'db dyn DefDatabase) -> bool {
pub fn is_block_module(self, db: &dyn DefDatabase) -> bool {
self.block(db).is_some() && self.def_map(db).root_module_id() == self
}
}
impl ModuleId {
/// # Safety
///
/// The caller must ensure that the `ModuleId` comes from the given database.
pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> ModuleIdLt<'db> {
unsafe { std::mem::transmute(self) }
}
}
impl HasModule for ModuleId {
#[inline]
fn module(&self, _db: &dyn DefDatabase) -> ModuleId {
@@ -75,7 +75,7 @@
use tt::TextRange;
use crate::{
AstId, BlockId, BlockIdLt, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles,
AstId, BlockId, BlockLoc, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles,
MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId,
db::DefDatabase,
item_scope::{BuiltinShadowMode, ItemScope},
@@ -247,12 +247,12 @@ struct BlockInfo {
parent: ModuleId,
}
impl std::ops::Index<ModuleIdLt<'_>> for DefMap {
impl std::ops::Index<ModuleId> for DefMap {
type Output = ModuleData;
fn index(&self, id: ModuleIdLt<'_>) -> &ModuleData {
fn index(&self, id: ModuleId) -> &ModuleData {
self.modules
.get(&unsafe { id.to_static() })
.get(&id)
.unwrap_or_else(|| panic!("ModuleId not found in ModulesMap {:#?}: {id:#?}", self.root))
}
}
@@ -400,10 +400,8 @@ pub(crate) fn crate_local_def_map(db: &dyn DefDatabase, crate_id: Crate) -> DefM
}
#[salsa_macros::tracked(returns(ref))]
pub fn block_def_map<'db>(db: &'db dyn DefDatabase, block_id: BlockIdLt<'db>) -> DefMap {
let block_id = unsafe { block_id.to_static() };
let ast_id = block_id.ast_id(db);
let module = unsafe { block_id.module(db).to_static() };
pub fn block_def_map(db: &dyn DefDatabase, block_id: BlockId) -> DefMap {
let BlockLoc { ast_id, module } = block_id.lookup(db);
let visibility = Visibility::Module(module, VisibilityExplicitness::Implicit);
let module_data =
@@ -559,7 +557,7 @@ pub fn parent(&self) -> Option<ModuleId> {
/// Returns the module containing `local_mod`, either the parent `mod`, or the module (or block) containing
/// the block, if `self` corresponds to a block expression.
pub fn containing_module(&self, local_mod: ModuleIdLt<'_>) -> Option<ModuleId> {
pub fn containing_module(&self, local_mod: ModuleId) -> Option<ModuleId> {
match self[local_mod].parent {
Some(parent) => Some(parent),
None => self.block.map(|BlockInfo { parent, .. }| parent),
@@ -664,11 +662,11 @@ pub(crate) fn resolve_path_locally(
///
/// If `f` returns `Some(val)`, iteration is stopped and `Some(val)` is returned. If `f` returns
/// `None`, iteration continues.
pub(crate) fn with_ancestor_maps<'db, T>(
pub(crate) fn with_ancestor_maps<T>(
&self,
db: &'db dyn DefDatabase,
local_mod: ModuleIdLt<'db>,
f: &mut dyn FnMut(&DefMap, ModuleIdLt<'db>) -> Option<T>,
db: &dyn DefDatabase,
local_mod: ModuleId,
f: &mut dyn FnMut(&DefMap, ModuleId) -> Option<T>,
) -> Option<T> {
if let Some(it) = f(self, local_mod) {
return Some(it);
@@ -854,13 +852,11 @@ fn deref_mut(&mut self) -> &mut Self::Target {
}
}
impl Index<ModuleIdLt<'_>> for ModulesMap {
impl Index<ModuleId> for ModulesMap {
type Output = ModuleData;
fn index(&self, id: ModuleIdLt<'_>) -> &ModuleData {
self.inner
.get(&unsafe { id.to_static() })
.unwrap_or_else(|| panic!("ModuleId not found in ModulesMap: {id:#?}"))
fn index(&self, id: ModuleId) -> &ModuleData {
self.inner.get(&id).unwrap_or_else(|| panic!("ModuleId not found in ModulesMap: {id:#?}"))
}
}
@@ -881,7 +881,7 @@ fn append_expr_scope<'db>(
}));
if let Some(block) = expr_scopes.block(scope_id) {
let def_map = block_def_map(db, block);
let local_def_map = block.module(db).only_local_def_map(db);
let local_def_map = block.lookup(db).module.only_local_def_map(db);
resolver.scopes.push(Scope::BlockScope(ModuleItemMap {
def_map,
local_def_map,
@@ -1087,7 +1087,7 @@ fn resolver_for_scope_<'db>(
for scope in scope_chain.into_iter().rev() {
if let Some(block) = scopes.block(scope) {
let def_map = block_def_map(db, block);
let local_def_map = block.module(db).only_local_def_map(db);
let local_def_map = block.lookup(db).module.only_local_def_map(db);
// Using `DefMap::ROOT` is okay here since inside modules other than the root,
// there can't directly be expressions.
r = r.push_block_scope(def_map, local_def_map, def_map.root);
@@ -9,8 +9,8 @@
use triomphe::Arc;
use crate::{
AssocItemId, HasModule, ItemContainerId, LocalFieldId, ModuleId, ModuleIdLt, TraitId,
VariantId, db::DefDatabase, nameres::DefMap, resolver::HasResolver, src::HasSource,
AssocItemId, HasModule, ItemContainerId, LocalFieldId, ModuleId, TraitId, VariantId,
db::DefDatabase, nameres::DefMap, resolver::HasResolver, src::HasSource,
};
pub use crate::item_tree::{RawVisibility, VisibilityExplicitness};
@@ -41,13 +41,9 @@ pub(crate) fn is_visible_from_other_crate(self) -> bool {
}
#[tracing::instrument(skip_all)]
pub fn is_visible_from<'db>(
self,
db: &'db dyn DefDatabase,
from_module: ModuleIdLt<'db>,
) -> bool {
pub fn is_visible_from(self, db: &dyn DefDatabase, from_module: ModuleId) -> bool {
let to_module = match self {
Visibility::Module(m, _) => unsafe { m.to_db(db) },
Visibility::Module(m, _) => m,
Visibility::PubCrate(krate) => return from_module.krate(db) == krate,
Visibility::Public => return true,
};
@@ -63,11 +59,11 @@ pub fn is_visible_from<'db>(
Self::is_visible_from_def_map_(db, def_map, to_module, from_module)
}
pub(crate) fn is_visible_from_def_map<'db>(
pub(crate) fn is_visible_from_def_map(
self,
db: &'db dyn DefDatabase,
def_map: &'db DefMap,
from_module: ModuleIdLt<'db>,
db: &dyn DefDatabase,
def_map: &DefMap,
from_module: ModuleId,
) -> bool {
if cfg!(debug_assertions) {
_ = def_map.modules[from_module];
@@ -93,11 +89,11 @@ pub(crate) fn is_visible_from_def_map<'db>(
Self::is_visible_from_def_map_(db, def_map, to_module, from_module)
}
fn is_visible_from_def_map_<'db>(
db: &'db dyn DefDatabase,
def_map: &'db DefMap,
mut to_module: ModuleIdLt<'db>,
mut from_module: ModuleIdLt<'db>,
fn is_visible_from_def_map_(
db: &dyn DefDatabase,
def_map: &DefMap,
mut to_module: ModuleId,
mut from_module: ModuleId,
) -> bool {
debug_assert_eq!(to_module.krate(db), def_map.krate());
// `to_module` might be the root module of a block expression. Those have the same
@@ -427,9 +427,14 @@ fn receiver_is_dispatchable<'db>(
};
let meta_sized_did = lang_items.MetaSized;
let Some(meta_sized_did) = meta_sized_did else {
return false;
};
// TODO: This is for supporting dyn compatibility for toolchains doesn't contain `MetaSized`
// trait. Uncomment and short circuit here once `MINIMUM_SUPPORTED_TOOLCHAIN_VERSION`
// become > 1.88.0
//
// let Some(meta_sized_did) = meta_sized_did else {
// return false;
// };
// Type `U`
// FIXME: That seems problematic to fake a generic param like that?
@@ -450,17 +455,16 @@ fn receiver_is_dispatchable<'db>(
});
let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args);
let meta_sized_predicate =
TraitRef::new(interner, meta_sized_did.into(), [unsized_self_ty]);
let meta_sized_predicate = meta_sized_did
.map(|did| TraitRef::new(interner, did.into(), [unsized_self_ty]).upcast(interner));
ParamEnv {
clauses: Clauses::new_from_iter(
interner,
generic_predicates.iter_identity_copied().chain([
unsize_predicate.upcast(interner),
trait_predicate.upcast(interner),
meta_sized_predicate.upcast(interner),
]),
generic_predicates
.iter_identity_copied()
.chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)])
.chain(meta_sized_predicate),
),
}
};
@@ -43,6 +43,7 @@ fn ty(&self, ctx: &mut InferenceContext<'_, 'db>) -> Ty<'db> {
for p in &self.projections {
ty = p.projected_ty(
&ctx.table.infer_ctxt,
ctx.table.param_env,
ty,
|_, _, _| {
unreachable!("Closure field only happens in MIR");
@@ -839,6 +840,7 @@ fn restrict_precision_for_unsafe(&mut self) {
for (i, p) in capture.place.projections.iter().enumerate() {
ty = p.projected_ty(
&self.table.infer_ctxt,
self.table.param_env,
ty,
|_, _, _| {
unreachable!("Closure field only happens in MIR");
@@ -25,7 +25,7 @@
consteval::try_const_usize,
db::HirDatabase,
next_solver::{
DbInterner, GenericArgs, ParamEnv, Ty, TyKind, TypingMode,
DbInterner, GenericArgs, Ty, TyKind, TypingMode,
infer::{DbInternerInferExt, traits::ObligationCause},
},
};
@@ -170,7 +170,7 @@ pub fn layout_of_ty_query<'db>(
let cx = LayoutCx::new(dl);
let infer_ctxt = interner.infer_ctxt().build(TypingMode::PostAnalysis);
let cause = ObligationCause::dummy();
let ty = infer_ctxt.at(&cause, ParamEnv::empty()).deeply_normalize(ty).unwrap_or(ty);
let ty = infer_ctxt.at(&cause, trait_env.param_env).deeply_normalize(ty).unwrap_or(ty);
let result = match ty.kind() {
TyKind::Adt(def, args) => {
match def.inner().id {
@@ -13,8 +13,8 @@
use base_db::Crate;
use hir_def::{
AssocItemId, BlockIdLt, ConstId, FunctionId, GenericParamId, HasModule, ImplId,
ItemContainerId, ModuleId, TraitId,
AssocItemId, BlockId, ConstId, FunctionId, GenericParamId, HasModule, ImplId, ItemContainerId,
ModuleId, TraitId,
attrs::AttrFlags,
expr_store::path::GenericArgs as HirGenericArgs,
hir::ExprId,
@@ -558,9 +558,9 @@ pub struct InherentImpls {
}
#[salsa::tracked]
impl<'db> InherentImpls {
impl InherentImpls {
#[salsa::tracked(returns(ref))]
pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Self {
pub fn for_crate(db: &dyn HirDatabase, krate: Crate) -> Self {
let _p = tracing::info_span!("inherent_impls_in_crate_query", ?krate).entered();
let crate_def_map = crate_def_map(db, krate);
@@ -569,7 +569,7 @@ pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Self {
}
#[salsa::tracked(returns(ref))]
pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option<Box<Self>> {
pub fn for_block(db: &dyn HirDatabase, block: BlockId) -> Option<Box<Self>> {
let _p = tracing::info_span!("inherent_impls_in_block_query").entered();
let block_def_map = block_def_map(db, block);
@@ -627,13 +627,13 @@ pub fn for_self_ty(&self, self_ty: &SimplifiedType) -> &[ImplId] {
self.map.get(self_ty).map(|it| &**it).unwrap_or_default()
}
pub fn for_each_crate_and_block<'db>(
db: &'db dyn HirDatabase,
pub fn for_each_crate_and_block(
db: &dyn HirDatabase,
krate: Crate,
block: Option<BlockIdLt<'db>>,
block: Option<BlockId>,
for_each: &mut dyn FnMut(&InherentImpls),
) {
let blocks = std::iter::successors(block, |block| block.module(db).block(db));
let blocks = std::iter::successors(block, |block| block.loc(db).module.block(db));
blocks.filter_map(|block| Self::for_block(db, block).as_deref()).for_each(&mut *for_each);
for_each(Self::for_crate(db, krate));
}
@@ -670,9 +670,9 @@ pub struct TraitImpls {
}
#[salsa::tracked]
impl<'db> TraitImpls {
impl TraitImpls {
#[salsa::tracked(returns(ref))]
pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Arc<Self> {
pub fn for_crate(db: &dyn HirDatabase, krate: Crate) -> Arc<Self> {
let _p = tracing::info_span!("inherent_impls_in_crate_query", ?krate).entered();
let crate_def_map = crate_def_map(db, krate);
@@ -681,7 +681,7 @@ pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Arc<Self> {
}
#[salsa::tracked(returns(ref))]
pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option<Box<Self>> {
pub fn for_block(db: &dyn HirDatabase, block: BlockId) -> Option<Box<Self>> {
let _p = tracing::info_span!("inherent_impls_in_block_query").entered();
let block_def_map = block_def_map(db, block);
@@ -690,7 +690,7 @@ pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option<Box<
}
#[salsa::tracked(returns(ref))]
pub fn for_crate_and_deps(db: &'db dyn HirDatabase, krate: Crate) -> Box<[Arc<Self>]> {
pub fn for_crate_and_deps(db: &dyn HirDatabase, krate: Crate) -> Box<[Arc<Self>]> {
krate.transitive_deps(db).iter().map(|&dep| Self::for_crate(db, dep).clone()).collect()
}
}
@@ -792,23 +792,23 @@ pub fn for_self_ty(&self, self_ty: &SimplifiedType, mut callback: impl FnMut(&[I
}
}
pub fn for_each_crate_and_block<'db>(
db: &'db dyn HirDatabase,
pub fn for_each_crate_and_block(
db: &dyn HirDatabase,
krate: Crate,
block: Option<BlockIdLt<'db>>,
block: Option<BlockId>,
for_each: &mut dyn FnMut(&TraitImpls),
) {
let blocks = std::iter::successors(block, |block| block.module(db).block(db));
let blocks = std::iter::successors(block, |block| block.loc(db).module.block(db));
blocks.filter_map(|block| Self::for_block(db, block).as_deref()).for_each(&mut *for_each);
Self::for_crate_and_deps(db, krate).iter().map(|it| &**it).for_each(for_each);
}
/// Like [`Self::for_each_crate_and_block()`], but takes in account two blocks, one for a trait and one for a self type.
pub fn for_each_crate_and_block_trait_and_type<'db>(
db: &'db dyn HirDatabase,
pub fn for_each_crate_and_block_trait_and_type(
db: &dyn HirDatabase,
krate: Crate,
type_block: Option<BlockIdLt<'db>>,
trait_block: Option<BlockIdLt<'db>>,
type_block: Option<BlockId>,
trait_block: Option<BlockId>,
for_each: &mut dyn FnMut(&TraitImpls),
) {
let in_self_and_deps = TraitImpls::for_crate_and_deps(db, krate);
@@ -819,11 +819,10 @@ pub fn for_each_crate_and_block_trait_and_type<'db>(
// that means there can't be duplicate impls; if they meet, we stop the search of the deeper block.
// This breaks when they are equal (both will stop immediately), therefore we handle this case
// specifically.
let blocks_iter = |block: Option<BlockIdLt<'db>>| {
std::iter::successors(block, |block| block.module(db).block(db))
let blocks_iter = |block: Option<BlockId>| {
std::iter::successors(block, |block| block.loc(db).module.block(db))
};
let for_each_block = |current_block: Option<BlockIdLt<'db>>,
other_block: Option<BlockIdLt<'db>>| {
let for_each_block = |current_block: Option<BlockId>, other_block: Option<BlockId>| {
blocks_iter(current_block)
.take_while(move |&block| {
other_block.is_none_or(|other_block| other_block != block)
@@ -157,6 +157,7 @@ impl<'db, V: PartialEq> ProjectionElem<'db, V> {
pub fn projected_ty(
&self,
infcx: &InferCtxt<'db>,
env: ParamEnv<'db>,
mut base: Ty<'db>,
closure_field: impl FnOnce(InternedClosureId, GenericArgs<'db>, usize) -> Ty<'db>,
krate: Crate,
@@ -173,8 +174,6 @@ pub fn projected_ty(
if matches!(base.kind(), TyKind::Alias(..)) {
let mut ocx = ObligationCtxt::new(infcx);
// FIXME: we should get this from caller
let env = ParamEnv::empty();
match ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, base) {
Ok(it) => base = it,
Err(_) => return Ty::new_error(interner, ErrorGuaranteed),
@@ -106,7 +106,7 @@ pub fn borrowck_query<'db>(
// FIXME(next-solver): Opaques.
let infcx = interner.infer_ctxt().build(typing_mode);
res.push(BorrowckResult {
mutability_of_locals: mutability_of_locals(&infcx, &body),
mutability_of_locals: mutability_of_locals(&infcx, env, &body),
moved_out_of_ref: moved_out_of_ref(&infcx, env, &body),
partially_moved: partially_moved(&infcx, env, &body),
borrow_regions: borrow_regions(db, &body),
@@ -146,6 +146,7 @@ fn moved_out_of_ref<'db>(
}
ty = proj.projected_ty(
infcx,
env,
ty,
make_fetch_closure_field(db),
body.owner.module(db).krate(db),
@@ -242,6 +243,7 @@ fn partially_moved<'db>(
for proj in p.projection.lookup(&body.projection_store) {
ty = proj.projected_ty(
infcx,
env,
ty,
make_fetch_closure_field(db),
body.owner.module(db).krate(db),
@@ -374,6 +376,7 @@ enum ProjectionCase {
fn place_case<'db>(
infcx: &InferCtxt<'db>,
env: ParamEnv<'db>,
body: &MirBody<'db>,
lvalue: &Place<'db>,
) -> ProjectionCase {
@@ -395,6 +398,7 @@ fn place_case<'db>(
}
ty = proj.projected_ty(
infcx,
env,
ty,
make_fetch_closure_field(db),
body.owner.module(db).krate(db),
@@ -535,6 +539,7 @@ fn record_usage_for_operand<'db>(
fn mutability_of_locals<'db>(
infcx: &InferCtxt<'db>,
env: ParamEnv<'db>,
body: &MirBody<'db>,
) -> ArenaMap<LocalId<'db>, MutabilityReason> {
let db = infcx.interner.db;
@@ -547,7 +552,7 @@ fn mutability_of_locals<'db>(
for statement in &block.statements {
match &statement.kind {
StatementKind::Assign(place, value) => {
match place_case(infcx, body, place) {
match place_case(infcx, env, body, place) {
ProjectionCase::Direct => {
if ever_init_map.get(place.local).copied().unwrap_or_default() {
push_mut_span(place.local, statement.span, &mut result);
@@ -596,7 +601,7 @@ fn mutability_of_locals<'db>(
},
p,
) = value
&& place_case(infcx, body, p) != ProjectionCase::Indirect
&& place_case(infcx, env, body, p) != ProjectionCase::Indirect
{
push_mut_span(p.local, statement.span, &mut result);
}
@@ -722,6 +722,7 @@ fn projected_ty(&self, ty: Ty<'db>, proj: PlaceElem<'db>) -> Ty<'db> {
let (ty, proj) = pair;
let r = proj.projected_ty(
&self.infcx,
self.param_env.param_env,
ty,
|c, subst, f| {
let InternedClosure(def, _) = self.db.lookup_intern_closure(c);
@@ -1,3 +1,4 @@
use hir_def::DefWithBodyId;
use test_fixture::WithFixture;
use crate::{db::HirDatabase, setup_tracing, test_db::TestDB};
@@ -49,3 +50,61 @@ fn foo() {
"#,
);
}
fn check_borrowck(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
let _tracing = setup_tracing();
let (db, file_ids) = TestDB::with_many_files(ra_fixture);
crate::attach_db(&db, || {
let file_id = *file_ids.last().unwrap();
let module_id = db.module_for_file(file_id.file_id(&db));
let def_map = module_id.def_map(&db);
let scope = &def_map[module_id].scope;
let mut bodies: Vec<DefWithBodyId> = Vec::new();
for decl in scope.declarations() {
if let hir_def::ModuleDefId::FunctionId(f) = decl {
bodies.push(f.into());
}
}
for impl_id in scope.impls() {
let impl_items = impl_id.impl_items(&db);
for (_, item) in impl_items.items.iter() {
if let hir_def::AssocItemId::FunctionId(f) = item {
bodies.push((*f).into());
}
}
}
for body in bodies {
let _ = db.borrowck(body);
}
})
}
#[test]
fn regression_21173_const_generic_impl_with_assoc_type() {
check_borrowck(
r#"
pub trait Tr {
type Assoc;
fn f(&self, handle: Self::Assoc) -> i32;
}
pub struct ConstGeneric<const N: usize>;
impl<const N: usize> Tr for &ConstGeneric<N> {
type Assoc = AssocTy;
fn f(&self, a: Self::Assoc) -> i32 {
a.x
}
}
pub struct AssocTy {
x: i32,
}
"#,
);
}
@@ -36,10 +36,6 @@ pub struct ObligationCause {
}
impl ObligationCause {
#[expect(
clippy::new_without_default,
reason = "`new` is temporary, eventually we will provide span etc. here"
)]
#[inline]
pub fn new() -> ObligationCause {
ObligationCause { _private: () }
@@ -1104,14 +1104,7 @@ fn variances_of(self, def_id: Self::DefId) -> Self::VariancesOf {
fn type_of(self, def_id: Self::DefId) -> EarlyBinder<Self, Self::Ty> {
match def_id {
SolverDefId::TypeAliasId(id) => {
use hir_def::Lookup;
match id.lookup(self.db()).container {
ItemContainerId::ImplId(it) => it,
_ => panic!("assoc ty value should be in impl"),
};
self.db().ty(id.into())
}
SolverDefId::TypeAliasId(id) => self.db().ty(id.into()),
SolverDefId::AdtId(id) => self.db().ty(id.into()),
// FIXME(next-solver): This uses the types of `query mir_borrowck` in rustc.
//
@@ -177,45 +177,52 @@ fn fetch_eligible_assoc_item(
impl_id: ImplIdWrapper,
) -> Result<Option<SolverDefId>, ErrorGuaranteed> {
let impl_items = impl_id.0.impl_items(self.0.interner.db());
let id = match trait_assoc_def_id {
SolverDefId::TypeAliasId(trait_assoc_id) => {
let trait_assoc_data = self.0.interner.db.type_alias_signature(trait_assoc_id);
impl_items
.items
.iter()
.find_map(|(impl_assoc_name, impl_assoc_id)| {
if let AssocItemId::TypeAliasId(impl_assoc_id) = *impl_assoc_id
&& *impl_assoc_name == trait_assoc_data.name
{
Some(impl_assoc_id)
} else {
None
}
})
.map(SolverDefId::TypeAliasId)
}
SolverDefId::ConstId(trait_assoc_id) => {
let trait_assoc_data = self.0.interner.db.const_signature(trait_assoc_id);
let trait_assoc_name = trait_assoc_data
.name
.as_ref()
.expect("unnamed consts should not get passed to the solver");
impl_items
.items
.iter()
.find_map(|(impl_assoc_name, impl_assoc_id)| {
if let AssocItemId::ConstId(impl_assoc_id) = *impl_assoc_id
&& impl_assoc_name == trait_assoc_name
{
Some(impl_assoc_id)
} else {
None
}
})
.map(SolverDefId::ConstId)
}
_ => panic!("Unexpected SolverDefId"),
};
let id =
match trait_assoc_def_id {
SolverDefId::TypeAliasId(trait_assoc_id) => {
let trait_assoc_data = self.0.interner.db.type_alias_signature(trait_assoc_id);
impl_items
.items
.iter()
.find_map(|(impl_assoc_name, impl_assoc_id)| {
if let AssocItemId::TypeAliasId(impl_assoc_id) = *impl_assoc_id
&& *impl_assoc_name == trait_assoc_data.name
{
Some(impl_assoc_id)
} else {
None
}
})
.or_else(|| {
if trait_assoc_data.ty.is_some() { Some(trait_assoc_id) } else { None }
})
.map(SolverDefId::TypeAliasId)
}
SolverDefId::ConstId(trait_assoc_id) => {
let trait_assoc_data = self.0.interner.db.const_signature(trait_assoc_id);
let trait_assoc_name = trait_assoc_data
.name
.as_ref()
.expect("unnamed consts should not get passed to the solver");
impl_items
.items
.iter()
.find_map(|(impl_assoc_name, impl_assoc_id)| {
if let AssocItemId::ConstId(impl_assoc_id) = *impl_assoc_id
&& impl_assoc_name == trait_assoc_name
{
Some(impl_assoc_id)
} else {
None
}
})
.or_else(|| {
if trait_assoc_data.has_body() { Some(trait_assoc_id) } else { None }
})
.map(SolverDefId::ConstId)
}
_ => panic!("Unexpected SolverDefId"),
};
Ok(id)
}
@@ -225,7 +232,9 @@ fn is_transmutable(
_src: Ty<'db>,
_assume: <Self::Interner as rustc_type_ir::Interner>::Const,
) -> Result<Certainty, NoSolution> {
unimplemented!()
// It's better to return some value while not fully implement
// then panic in the mean time
Ok(Certainty::Yes)
}
fn evaluate_const(
@@ -226,6 +226,62 @@ fn debug(_: &dyn Foo) {}
impl Foo for i32 {}
fn main() {
debug(&1);
}"#,
);
// toolchains <= 1.88.0, before sized-hierarchy.
check_no_mismatches(
r#"
#[lang = "sized"]
pub trait Sized {}
#[lang = "unsize"]
pub trait Unsize<T: ?Sized> {}
#[lang = "coerce_unsized"]
pub trait CoerceUnsized<T: ?Sized> {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {}
impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {}
impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a T {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *mut T {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *const T {}
#[lang = "dispatch_from_dyn"]
pub trait DispatchFromDyn<T> {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<&'a U> for &'a T {}
impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<&'a mut U> for &'a mut T {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<*const U> for *const T {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<*mut U> for *mut T {}
trait Foo {
fn bar(&self) -> u32 {
0xCAFE
}
}
fn debug(_: &dyn Foo) {}
impl Foo for i32 {}
fn main() {
debug(&1);
}"#,
@@ -5079,3 +5079,23 @@ fn foo(base_layer_two: &dyn BaseLayerOne) {
"#,
);
}
#[test]
fn default_assoc_types() {
check_types(
r#"
trait Trait<T> {
type Assoc<U> = (T, U);
fn method(self) -> Self::Assoc<i32> { loop {} }
}
struct Struct<T>(T);
impl<T> Trait<((), T)> for Struct<T> {}
fn foo(v: Struct<f32>) {
v.method();
// ^^^^^^^^^^ (((), f32), i32)
}
"#,
);
}
@@ -4,42 +4,5 @@
//!
//! But we need this for at least LRU caching at the query level.
pub use hir_def::db::DefDatabase;
// AttrsQuery, BlockDefMapQuery, BlockItemTreeQuery, BlockItemTreeWithSourceMapQuery, BodyQuery,
// BodyWithSourceMapQuery, ConstDataQuery, ConstVisibilityQuery, CrateDefMapQuery,
// CrateLangItemsQuery, CrateNotableTraitsQuery, CrateSupportsNoStdQuery, DefDatabase,
// DefDatabaseStorage, EnumDataQuery, EnumVariantDataWithDiagnosticsQuery,
// ExpandProcAttrMacrosQuery, ExprScopesQuery, ExternCrateDeclDataQuery, FieldVisibilitiesQuery,
// FieldsAttrsQuery, FieldsAttrsSourceMapQuery, FileItemTreeQuery, FileItemTreeWithSourceMapQuery,
// FunctionDataQuery, FunctionVisibilityQuery, GenericParamsQuery,
// GenericParamsWithSourceMapQuery, ImplItemsWithDiagnosticsQuery, ImportMapQuery,
// IncludeMacroInvocQuery, InternAnonymousConstQuery, InternBlockQuery, InternConstQuery,
// InternDatabase, InternDatabaseStorage, InternEnumQuery, InternExternBlockQuery,
// InternExternCrateQuery, InternFunctionQuery, InternImplQuery, InternInTypeConstQuery,
// InternMacro2Query, InternMacroRulesQuery, InternProcMacroQuery, InternStaticQuery,
// InternStructQuery, InternTraitAliasQuery, InternTraitQuery, InternTypeAliasQuery,
// InternUnionQuery, InternUseQuery, LangItemQuery, Macro2DataQuery, MacroDefQuery,
// MacroRulesDataQuery, NotableTraitsInDepsQuery, ProcMacroDataQuery, StaticDataQuery,
// StructDataWithDiagnosticsQuery, TraitAliasDataQuery, TraitItemsWithDiagnosticsQuery,
// TypeAliasDataQuery, UnionDataWithDiagnosticsQuery,
// };
pub use hir_expand::db::ExpandDatabase;
// AstIdMapQuery, DeclMacroExpanderQuery, ExpandDatabase, ExpandDatabaseStorage,
// ExpandProcMacroQuery, InternMacroCallQuery, InternSyntaxContextQuery, MacroArgQuery,
// ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, ProcMacroSpanQuery, ProcMacrosQuery,
// RealSpanMapQuery,
pub use hir_ty::db::HirDatabase;
// AdtDatumQuery, AdtVarianceQuery, AssociatedTyDataQuery, AssociatedTyValueQuery, BorrowckQuery,
// CallableItemSignatureQuery, ConstEvalDiscriminantQuery, ConstEvalQuery, ConstEvalStaticQuery,
// ConstParamTyQuery, DynCompatibilityOfTraitQuery, FieldTypesQuery, FnDefDatumQuery,
// FnDefVarianceQuery, GenericDefaultsQuery, GenericPredicatesForParamQuery,
// GenericPredicatesQuery, GenericPredicatesWithoutParentQuery, HirDatabase, HirDatabaseStorage,
// ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, IncoherentInherentImplCratesQuery, InferQuery,
// InherentImplsInBlockQuery, InherentImplsInCrateQuery, InternCallableDefQuery,
// InternClosureQuery, InternCoroutineQuery, InternImplTraitIdQuery, InternLifetimeParamIdQuery,
// InternTypeOrConstParamIdQuery, LayoutOfAdtQuery, LayoutOfTyQuery, LookupImplMethodQuery,
// MirBodyForClosureQuery, MirBodyQuery, MonomorphizedMirBodyForClosureQuery,
// MonomorphizedMirBodyQuery, ProgramClausesForChalkEnvQuery, ReturnTypeImplTraitsQuery,
// TargetDataLayoutQuery, TraitDatumQuery, TraitEnvironmentQuery, TraitImplsInBlockQuery,
// TraitImplsInCrateQuery, TraitImplsInDepsQuery, TraitSolveQuery, TyQuery,
// TypeAliasImplTraitsQuery, ValueTyQuery,
// };
@@ -192,9 +192,11 @@ fn write_impl_header<'db>(impl_: &Impl, f: &mut HirFormatter<'_, 'db>) -> Result
let def_id = GenericDefId::ImplId(impl_.id);
write_generic_params(def_id, f)?;
if let Some(trait_) = impl_.trait_(db) {
let trait_data = db.trait_signature(trait_.id);
write!(f, " {} for", trait_data.name.display(db, f.edition()))?;
let impl_data = db.impl_signature(impl_.id);
if let Some(target_trait) = &impl_data.target_trait {
f.write_char(' ')?;
hir_display_with_store(&impl_data.store[target_trait.path], &impl_data.store).hir_fmt(f)?;
f.write_str(" for")?;
}
f.write_char(' ')?;
@@ -590,7 +590,7 @@ pub fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module {
while id.is_block_module(db) {
id = id.containing_module(db).expect("block without parent module");
}
Module { id: unsafe { id.to_static() } }
Module { id }
}
pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec<Module> {
@@ -4352,7 +4352,7 @@ pub fn all_for_type<'db>(
module.block(db),
&mut |impls| extend_with_impls(impls.for_self_ty(&simplified_ty)),
);
std::iter::successors(module.block(db), |block| block.module(db).block(db))
std::iter::successors(module.block(db), |block| block.loc(db).module.block(db))
.filter_map(|block| TraitImpls::for_block(db, block).as_deref())
.for_each(|impls| impls.for_self_ty(&simplified_ty, &mut extend_with_impls));
for &krate in &**db.all_crates() {
@@ -267,7 +267,7 @@ pub fn lint_attrs(
&self,
krate: Crate,
item: ast::AnyHasAttrs,
) -> impl Iterator<Item = (LintAttr, SmolStr)> {
) -> impl DoubleEndedIterator<Item = (LintAttr, SmolStr)> {
let mut cfg_options = None;
let cfg_options = || *cfg_options.get_or_insert_with(|| krate.id.cfg_options(self.db));
let mut result = Vec::new();
@@ -226,7 +226,7 @@ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: Hi
// All block expressions are merged into the same map, because they logically all add
// inner items to the containing `DefWithBodyId`.
def_map[def_map.root].scope.child_by_source_to(db, res, file_id);
res[keys::BLOCK].insert(block.ast_id(db).to_ptr(db), block);
res[keys::BLOCK].insert(block.lookup(db).ast_id.to_ptr(db), block);
}
}
}
@@ -1,5 +1,7 @@
//! File symbol extraction.
use std::marker::PhantomData;
use base_db::FxIndexSet;
use either::Either;
use hir_def::{
@@ -25,7 +27,7 @@
/// The actual data that is stored in the index. It should be as compact as
/// possible.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FileSymbol {
pub struct FileSymbol<'db> {
pub name: Symbol,
pub def: ModuleDef,
pub loc: DeclarationLocation,
@@ -35,6 +37,7 @@ pub struct FileSymbol {
pub is_assoc: bool,
pub is_import: bool,
pub do_not_complete: Complete,
_marker: PhantomData<&'db ()>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -61,9 +64,9 @@ struct SymbolCollectorWork {
parent: Option<Name>,
}
pub struct SymbolCollector<'a> {
db: &'a dyn HirDatabase,
symbols: FxIndexSet<FileSymbol>,
pub struct SymbolCollector<'db> {
db: &'db dyn HirDatabase,
symbols: FxIndexSet<FileSymbol<'db>>,
work: Vec<SymbolCollectorWork>,
current_container_name: Option<Symbol>,
collect_pub_only: bool,
@@ -83,10 +86,10 @@ pub fn new(db: &'a dyn HirDatabase, collect_pub_only: bool) -> Self {
}
pub fn new_module(
db: &dyn HirDatabase,
db: &'a dyn HirDatabase,
module: Module,
collect_pub_only: bool,
) -> Box<[FileSymbol]> {
) -> Box<[FileSymbol<'a>]> {
let mut symbol_collector = SymbolCollector::new(db, collect_pub_only);
symbol_collector.collect(module);
symbol_collector.finish()
@@ -105,7 +108,7 @@ pub fn collect(&mut self, module: Module) {
}
}
pub fn finish(self) -> Box<[FileSymbol]> {
pub fn finish(self) -> Box<[FileSymbol<'a>]> {
self.symbols.into_iter().collect()
}
@@ -217,6 +220,7 @@ fn collect_from_module(&mut self, module_id: ModuleId) {
is_assoc: false,
is_import: true,
do_not_complete: Complete::Yes,
_marker: PhantomData,
});
};
@@ -251,6 +255,7 @@ fn collect_from_module(&mut self, module_id: ModuleId) {
is_assoc: false,
is_import: false,
do_not_complete: Complete::Yes,
_marker: PhantomData,
});
};
@@ -428,6 +433,7 @@ fn push_decl<L>(
is_assoc,
is_import: false,
do_not_complete,
_marker: PhantomData,
});
}
}
@@ -441,6 +447,7 @@ fn push_decl<L>(
is_assoc,
is_import: false,
do_not_complete,
_marker: PhantomData,
});
do_not_complete
@@ -474,6 +481,7 @@ fn push_module(&mut self, module_id: ModuleId, name: &Name) {
is_assoc: false,
is_import: false,
do_not_complete,
_marker: PhantomData,
});
}
}
@@ -487,6 +495,7 @@ fn push_module(&mut self, module_id: ModuleId, name: &Name) {
is_assoc: false,
is_import: false,
do_not_complete,
_marker: PhantomData,
});
}
}
@@ -33,7 +33,7 @@ pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
return None;
}
let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
let func = param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?;
let stmt_list = func.body()?.stmt_list()?;
let l_curly_range = stmt_list.l_curly_token()?.text_range();
let r_curly_range = stmt_list.r_curly_token()?.text_range();
@@ -176,6 +176,18 @@ fn keep_underscore_used() {
bind_unused_param,
r#"
fn foo($0_x: i32, y: i32) {}
"#,
);
}
#[test]
fn not_applicable_closure() {
check_assist_not_applicable(
bind_unused_param,
r#"
fn foo() {
let _ = |$0x| 2;
}
"#,
);
}
@@ -1,11 +1,8 @@
use hir::{
Name,
sym::{self},
};
use hir::{Name, sym};
use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name};
use syntax::{
AstNode,
ast::{self, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
syntax_editor::Position,
};
@@ -82,6 +79,18 @@ pub(crate) fn convert_for_loop_to_while_let(
Some(iterable),
);
let indent = IndentLevel::from_node(for_loop.syntax());
if let Some(label) = for_loop.label() {
let label = label.syntax().clone_for_update();
editor.insert(Position::before(for_loop.syntax()), make.whitespace(" "));
editor.insert(Position::before(for_loop.syntax()), label);
}
crate::utils::insert_attributes(
for_loop.syntax(),
&mut editor,
for_loop.attrs().map(|it| it.clone_for_update()),
);
editor.insert(
Position::before(for_loop.syntax()),
make::tokens::whitespace(format!("\n{indent}").as_str()),
@@ -186,6 +195,56 @@ fn main() {
)
}
#[test]
fn each_to_for_with_label() {
check_assist(
convert_for_loop_to_while_let,
r"
fn main() {
let mut x = vec![1, 2, 3];
'a: for $0v in x {
v *= 2;
break 'a;
};
}",
r"
fn main() {
let mut x = vec![1, 2, 3];
let mut tmp = x.into_iter();
'a: while let Some(v) = tmp.next() {
v *= 2;
break 'a;
};
}",
)
}
#[test]
fn each_to_for_with_attributes() {
check_assist(
convert_for_loop_to_while_let,
r"
fn main() {
let mut x = vec![1, 2, 3];
#[allow(unused)]
#[deny(unsafe_code)]
for $0v in x {
v *= 2;
};
}",
r"
fn main() {
let mut x = vec![1, 2, 3];
let mut tmp = x.into_iter();
#[allow(unused)]
#[deny(unsafe_code)]
while let Some(v) = tmp.next() {
v *= 2;
};
}",
)
}
#[test]
fn each_to_for_for_in_range() {
check_assist(
@@ -3,7 +3,7 @@
use stdx::format_to;
use syntax::{
AstNode,
ast::{self, HasArgList, HasLoopBody, edit_in_place::Indent, make},
ast::{self, HasArgList, HasLoopBody, edit_in_place::Indent, syntax_factory::SyntaxFactory},
};
use crate::{AssistContext, AssistId, Assists};
@@ -57,18 +57,22 @@ pub(crate) fn convert_iter_for_each_to_for(
"Replace this `Iterator::for_each` with a for loop",
range,
|builder| {
let make = SyntaxFactory::with_mappings();
let indent =
stmt.as_ref().map_or_else(|| method.indent_level(), ast::ExprStmt::indent_level);
let block = match body {
ast::Expr::BlockExpr(block) => block,
_ => make::block_expr(Vec::new(), Some(body)),
}
.clone_for_update();
ast::Expr::BlockExpr(block) => block.clone_for_update(),
_ => make.block_expr(Vec::new(), Some(body)),
};
block.reindent_to(indent);
let expr_for_loop = make::expr_for_loop(param, receiver, block);
builder.replace(range, expr_for_loop.to_string())
let expr_for_loop = make.expr_for_loop(param, receiver, block);
let target_node = stmt.as_ref().map_or(method.syntax(), AstNode::syntax);
let mut editor = builder.make_editor(target_node);
editor.replace(target_node, expr_for_loop.syntax());
builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
@@ -1,6 +1,5 @@
use std::iter;
use either::Either;
use ide_db::syntax_helpers::node_ext::is_pattern_cond;
use syntax::{
AstNode, T,
@@ -9,6 +8,7 @@
edit::{AstNodeEdit, IndentLevel},
make,
},
syntax_editor::{Element, Position},
};
use crate::{
@@ -44,43 +44,53 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
let while_body = while_expr.loop_body()?;
let while_cond = while_expr.condition()?;
let l_curly = while_body.stmt_list()?.l_curly_token()?;
let target = while_expr.syntax().text_range();
acc.add(
AssistId::refactor_rewrite("convert_while_to_loop"),
"Convert while to loop",
target,
|edit| {
|builder| {
let mut edit = builder.make_editor(while_expr.syntax());
let while_indent_level = IndentLevel::from_node(while_expr.syntax());
let break_block = make::block_expr(
iter::once(make::expr_stmt(make::expr_break(None, None)).into()),
None,
)
.indent(while_indent_level);
let block_expr = if is_pattern_cond(while_cond.clone()) {
let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
.indent(IndentLevel(1));
edit.replace_all(
while_kw.syntax_element()..=while_cond.syntax().syntax_element(),
vec![make::token(T![loop]).syntax_element()],
);
if is_pattern_cond(while_cond.clone()) {
let then_branch = while_body.reset_indent().indent(IndentLevel(1));
let if_expr = make::expr_if(while_cond, then_branch, Some(break_block.into()));
let stmts = iter::once(make::expr_stmt(if_expr.into()).into());
make::block_expr(stmts, None)
let block_expr = make::block_expr(stmts, None);
edit.replace(while_body.syntax(), block_expr.indent(while_indent_level).syntax());
} else {
let if_cond = invert_boolean_expression_legacy(while_cond);
let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into();
let elements = while_body.stmt_list().map_or_else(
|| Either::Left(iter::empty()),
|stmts| {
Either::Right(stmts.syntax().children_with_tokens().filter(|node_or_tok| {
// Filter out the trailing expr
!node_or_tok
.as_node()
.is_some_and(|node| ast::Expr::can_cast(node.kind()))
}))
},
let if_expr = make::expr_if(if_cond, break_block, None).indent(while_indent_level);
if !while_body.syntax().text().contains_char('\n') {
edit.insert(
Position::after(&l_curly),
make::tokens::whitespace(&format!("\n{while_indent_level}")),
);
}
edit.insert_all(
Position::after(&l_curly),
vec![
make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(),
if_expr.syntax().syntax_element(),
],
);
make::hacky_block_expr(iter::once(if_expr).chain(elements), while_body.tail_expr())
};
let replacement = make::expr_loop(block_expr.indent(while_indent_level));
edit.replace(target, replacement.syntax().text())
builder.add_file_edits(ctx.vfs_file_id(), edit);
},
)
}
@@ -115,6 +125,110 @@ fn main() {
);
}
#[test]
fn convert_with_label() {
check_assist(
convert_while_to_loop,
r#"
fn main() {
'x: while$0 cond {
foo();
break 'x
}
}
"#,
r#"
fn main() {
'x: loop {
if !cond {
break;
}
foo();
break 'x
}
}
"#,
);
check_assist(
convert_while_to_loop,
r#"
fn main() {
'x: while$0 let Some(x) = cond {
foo();
break 'x
}
}
"#,
r#"
fn main() {
'x: loop {
if let Some(x) = cond {
foo();
break 'x
} else {
break;
}
}
}
"#,
);
}
#[test]
fn convert_with_attributes() {
check_assist(
convert_while_to_loop,
r#"
fn main() {
#[allow(unused)]
while$0 cond {
foo();
break 'x
}
}
"#,
r#"
fn main() {
#[allow(unused)]
loop {
if !cond {
break;
}
foo();
break 'x
}
}
"#,
);
check_assist(
convert_while_to_loop,
r#"
fn main() {
#[allow(unused)]
#[deny(unsafe_code)]
while$0 let Some(x) = cond {
foo();
}
}
"#,
r#"
fn main() {
#[allow(unused)]
#[deny(unsafe_code)]
loop {
if let Some(x) = cond {
foo();
} else {
break;
}
}
}
"#,
);
}
#[test]
fn convert_busy_wait() {
check_assist(
@@ -185,6 +299,76 @@ fn main() {
);
}
#[test]
fn indentation() {
check_assist(
convert_while_to_loop,
r#"
fn main() {
{
{
while$0 cond {
foo(
"xxx",
);
}
}
}
}
"#,
r#"
fn main() {
{
{
loop {
if !cond {
break;
}
foo(
"xxx",
);
}
}
}
}
"#,
);
check_assist(
convert_while_to_loop,
r#"
fn main() {
{
{
while$0 let Some(_) = foo() {
bar(
"xxx",
);
}
}
}
}
"#,
r#"
fn main() {
{
{
loop {
if let Some(_) = foo() {
bar(
"xxx",
);
} else {
break;
}
}
}
}
}
"#,
);
}
#[test]
fn ignore_cursor_in_body() {
check_assist_not_applicable(
@@ -14,15 +14,15 @@
};
use itertools::Itertools;
use syntax::{
AstNode, Edition, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr,
AstNode, Edition, SmolStr, SyntaxElement, SyntaxKind, ToSmolStr,
ast::{
self, AssocItem, GenericArgList, GenericParamList, HasAttrs, HasGenericArgs,
HasGenericParams, HasName, HasTypeBounds, HasVisibility as astHasVisibility, Path,
WherePred,
edit::{self, AstNodeEdit},
make,
syntax_factory::SyntaxFactory,
},
ted::{self, Position},
syntax_editor::SyntaxEditor,
};
// Assist: generate_delegate_trait
@@ -169,10 +169,15 @@ enum Delegee {
}
impl Delegee {
fn trait_(&self) -> &hir::Trait {
match self {
Delegee::Bound(it) | Delegee::Impls(it, _) => it,
}
}
fn signature(&self, db: &dyn HirDatabase, edition: Edition) -> String {
let mut s = String::new();
let (Delegee::Bound(it) | Delegee::Impls(it, _)) = self;
let it = self.trait_();
for m in it.module(db).path_to_root(db).iter().rev() {
if let Some(name) = m.name(db) {
@@ -201,15 +206,12 @@ pub(crate) fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistConte
let db = ctx.db();
for (index, delegee) in field.impls.iter().enumerate() {
let trait_ = match delegee {
Delegee::Bound(b) => b,
Delegee::Impls(i, _) => i,
};
let trait_ = delegee.trait_();
// Skip trait that has `Self` type, which cannot be delegated
//
// See [`test_self_ty`]
if has_self_type(*trait_, ctx).is_some() {
if has_self_type(*trait_, ctx) {
continue;
}
@@ -254,9 +256,10 @@ fn generate_impl(
delegee: &Delegee,
edition: Edition,
) -> Option<ast::Impl> {
let make = SyntaxFactory::without_mappings();
let db = ctx.db();
let ast_strukt = &strukt.strukt;
let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string()));
let strukt_ty = make.ty_path(make.ident_path(&strukt.name.to_string())).into();
let strukt_params = ast_strukt.generic_param_list();
match delegee {
@@ -264,7 +267,7 @@ fn generate_impl(
let bound_def = ctx.sema.source(delegee.to_owned())?.value;
let bound_params = bound_def.generic_param_list();
let delegate = make::impl_trait(
let delegate = make.impl_trait(
None,
delegee.is_unsafe(db),
bound_params.clone(),
@@ -272,33 +275,28 @@ fn generate_impl(
strukt_params.clone(),
strukt_params.map(|params| params.to_generic_args()),
delegee.is_auto(db),
make::ty(&delegee.name(db).display_no_db(edition).to_smolstr()),
make.ty(&delegee.name(db).display_no_db(edition).to_smolstr()),
strukt_ty,
bound_def.where_clause(),
ast_strukt.where_clause(),
None,
)
.clone_for_update();
);
// Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
let qualified_path_type =
make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
if let Some(ai) = bound_def.assoc_item_list() {
// Collect assoc items
let assoc_items: Option<Vec<ast::AssocItem>> = bound_def.assoc_item_list().map(|ai| {
ai.assoc_items()
.filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
.for_each(|item| {
let assoc = process_assoc_item(
item.clone_for_update(),
qualified_path_type.clone(),
field_name,
);
if let Some(assoc) = assoc {
delegate_assoc_items.add_item(assoc);
}
});
};
.filter_map(|item| {
process_assoc_item(item, qualified_path_type.clone(), field_name)
})
.collect()
});
let delegate = finalize_delegate(&make, &delegate, assoc_items, false)?;
let target_scope = ctx.sema.scope(strukt.strukt.syntax())?;
let source_scope = ctx.sema.scope(bound_def.syntax())?;
@@ -324,7 +322,7 @@ fn generate_impl(
.and_then(|wc| rename_strukt_args(ctx, ast_strukt, &wc, &args));
(field_ty, where_clause)
}
None => (field_ty.clone_for_update(), None),
None => (field_ty.clone(), None),
};
// 2) Handle instantiated generics in `field_ty`.
@@ -347,38 +345,38 @@ fn generate_impl(
);
// 2.2) Generate generic args applied on impl.
let transform_args = generate_args_for_impl(
let (transform_args, trait_gen_params) = generate_args_for_impl(
old_impl_params,
&old_impl.self_ty()?,
&field_ty,
&trait_gen_params,
trait_gen_params,
&old_impl_trait_args,
);
// 2.3) Instantiate generics with `transform_impl`, this step also
// remove unused params.
let trait_gen_args = old_impl.trait_()?.generic_arg_list().and_then(|trait_args| {
let trait_args = &mut trait_args.clone_for_update();
if let Some(new_args) = transform_impl(
ctx,
ast_strukt,
&old_impl,
&transform_args,
trait_args.clone_subtree(),
) {
*trait_args = new_args.clone_subtree();
Some(new_args)
} else {
None
}
});
let trait_gen_args =
old_impl.trait_()?.generic_arg_list().and_then(|mut trait_args| {
let trait_args = &mut trait_args;
if let Some(new_args) = transform_impl(
ctx,
ast_strukt,
&old_impl,
&transform_args,
trait_args.clone_subtree(),
) {
*trait_args = new_args.clone_subtree();
Some(new_args)
} else {
None
}
});
let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args());
let path_type =
make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update();
let path_type = make.ty(&trait_.name(db).display_no_db(edition).to_smolstr());
let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?;
// 3) Generate delegate trait impl
let delegate = make::impl_trait(
let delegate = make.impl_trait(
None,
trait_.is_unsafe(db),
trait_gen_params,
@@ -388,34 +386,27 @@ fn generate_impl(
trait_.is_auto(db),
path_type,
strukt_ty,
old_impl.where_clause().map(|wc| wc.clone_for_update()),
old_impl.where_clause(),
ty_where_clause,
None,
)
.clone_for_update();
);
// Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
let qualified_path_type =
make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
// 4) Transform associated items in delegte trait impl
let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
for item in old_impl
.get_or_create_assoc_item_list()
.assoc_items()
.filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
{
let item = item.clone_for_update();
let item = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?;
// 4) Transform associated items in delegate trait impl
let assoc_items: Option<Vec<ast::AssocItem>> = old_impl.assoc_item_list().map(|ail| {
ail.assoc_items()
.filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
.filter_map(|item| {
let item =
transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?;
process_assoc_item(item, qualified_path_type.clone(), field_name)
})
.collect()
});
let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?;
delegate_assoc_items.add_item(assoc);
}
// 5) Remove useless where clauses
if let Some(wc) = delegate.where_clause() {
remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc);
}
Some(delegate)
finalize_delegate(&make, &delegate, assoc_items, true)
}
}
}
@@ -446,6 +437,35 @@ fn transform_impl<N: ast::AstNode>(
N::cast(transform.apply(syntax.syntax()))
}
/// Extracts the name from a generic parameter.
fn generic_param_name(param: &ast::GenericParam) -> Option<String> {
match param {
ast::GenericParam::TypeParam(t) => t.name().map(|n| n.to_string()),
ast::GenericParam::ConstParam(c) => c.name().map(|n| n.to_string()),
ast::GenericParam::LifetimeParam(l) => l.lifetime().map(|lt| lt.to_string()),
}
}
/// Filters generic params, keeping only those whose names are not in `names_to_remove`.
fn filter_generic_params(
gpl: ast::GenericParamList,
names_to_remove: &FxHashSet<String>,
) -> Option<ast::GenericParamList> {
let remaining_params: Vec<_> = gpl
.generic_params()
.filter(|param| {
generic_param_name(param).is_none_or(|name| !names_to_remove.contains(&name))
})
.collect();
if remaining_params.is_empty() {
None
} else {
let make = SyntaxFactory::without_mappings();
Some(make.generic_param_list(remaining_params))
}
}
fn remove_instantiated_params(
self_ty: &ast::Type,
old_impl_params: Option<GenericParamList>,
@@ -454,10 +474,8 @@ fn remove_instantiated_params(
match self_ty {
ast::Type::PathType(path_type) => {
old_impl_params.and_then(|gpl| {
// Remove generic parameters in field_ty (which is instantiated).
let new_gpl = gpl.clone_for_update();
path_type
// Collect generic args that should be removed (instantiated params)
let args_to_remove: FxHashSet<String> = path_type
.path()?
.segments()
.filter_map(|seg| seg.generic_arg_list())
@@ -466,16 +484,25 @@ fn remove_instantiated_params(
// it shouldn't be removed now, which will be instantiated in
// later `path_transform`
.filter(|arg| !old_trait_args.contains(&arg.to_string()))
.for_each(|arg| new_gpl.remove_generic_arg(&arg));
(new_gpl.generic_params().count() > 0).then_some(new_gpl)
.map(|arg| arg.to_string())
.collect();
filter_generic_params(gpl, &args_to_remove)
})
}
_ => old_impl_params,
}
}
fn remove_useless_where_clauses(trait_ty: &ast::Type, self_ty: &ast::Type, wc: ast::WhereClause) {
let live_generics = [trait_ty, self_ty]
fn remove_useless_where_clauses(editor: &mut SyntaxEditor, delegate: &ast::Impl) {
let Some(wc) = delegate.where_clause() else {
return;
};
let (Some(trait_ty), Some(self_ty)) = (delegate.trait_(), delegate.self_ty()) else {
return;
};
let live_generics = [&trait_ty, &self_ty]
.into_iter()
.flat_map(|ty| ty.generic_arg_list())
.flat_map(|gal| gal.generic_args())
@@ -484,36 +511,78 @@ fn remove_useless_where_clauses(trait_ty: &ast::Type, self_ty: &ast::Type, wc: a
// Keep where-clauses that have generics after substitution, and remove the
// rest.
let has_live_generics = |pred: &WherePred| {
let has_no_live_generics = |pred: &WherePred| {
pred.syntax()
.descendants_with_tokens()
.filter_map(|e| e.into_token())
.any(|e| e.kind() == SyntaxKind::IDENT && live_generics.contains(&e.to_string()))
.not()
};
wc.predicates().filter(has_live_generics).for_each(|pred| wc.remove_predicate(pred));
if wc.predicates().count() == 0 {
// Remove useless whitespaces
[syntax::Direction::Prev, syntax::Direction::Next]
.into_iter()
.flat_map(|dir| {
wc.syntax()
.siblings_with_tokens(dir)
.skip(1)
.take_while(|node_or_tok| node_or_tok.kind() == SyntaxKind::WHITESPACE)
})
.for_each(ted::remove);
let predicates_to_remove: Vec<_> = wc.predicates().filter(has_no_live_generics).collect();
let remaining_predicates = wc.predicates().count() - predicates_to_remove.len();
ted::insert(
ted::Position::after(wc.syntax()),
NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)),
);
// Remove where clause
ted::remove(wc.syntax());
if remaining_predicates == 0 {
// Remove the entire where clause
editor.delete(wc.syntax().clone());
} else {
// Remove only the useless predicates
for pred in predicates_to_remove {
// Also remove the comma before or after the predicate
if let Some(previous) = pred.syntax().prev_sibling() {
// Remove from after previous sibling to predicate (inclusive)
if let Some(start) = previous.next_sibling_or_token() {
let end: SyntaxElement = pred.syntax().clone().into();
editor.delete_all(start..=end);
}
} else if let Some(next) = pred.syntax().next_sibling() {
// Remove from predicate to before next sibling (exclusive)
if let Some(end) = next.prev_sibling_or_token() {
let start: SyntaxElement = pred.syntax().clone().into();
editor.delete_all(start..=end);
}
} else {
editor.delete(pred.syntax().clone());
}
}
}
}
/// Finalize the delegate impl by:
/// 1. Replacing the assoc_item_list with new items (if any)
/// 2. Removing useless where clauses
fn finalize_delegate(
make: &SyntaxFactory,
delegate: &ast::Impl,
assoc_items: Option<Vec<ast::AssocItem>>,
remove_where_clauses: bool,
) -> Option<ast::Impl> {
let has_items = assoc_items.as_ref().is_some_and(|items| !items.is_empty());
if !has_items && !remove_where_clauses {
return Some(delegate.clone());
}
let mut editor = SyntaxEditor::new(delegate.syntax().clone_subtree());
// 1. Replace assoc_item_list if we have new items
if let Some(items) = assoc_items
&& !items.is_empty()
{
let new_assoc_item_list = make.assoc_item_list(items);
if let Some(old_list) = delegate.assoc_item_list() {
editor.replace(old_list.syntax(), new_assoc_item_list.syntax());
}
}
// 2. Remove useless where clauses
if remove_where_clauses {
remove_useless_where_clauses(&mut editor, delegate);
}
ast::Impl::cast(editor.finish().new_root().clone())
}
// Generate generic args that should be apply to current impl.
//
// For example, say we have implementation `impl<A, B, C> Trait for B<A>`,
@@ -524,10 +593,13 @@ fn generate_args_for_impl(
old_impl_gpl: Option<GenericParamList>,
self_ty: &ast::Type,
field_ty: &ast::Type,
trait_params: &Option<GenericParamList>,
trait_params: Option<GenericParamList>,
old_trait_args: &FxHashSet<String>,
) -> Option<ast::GenericArgList> {
let old_impl_args = old_impl_gpl.map(|gpl| gpl.to_generic_args().generic_args())?;
) -> (Option<ast::GenericArgList>, Option<GenericParamList>) {
let Some(old_impl_args) = old_impl_gpl.map(|gpl| gpl.to_generic_args().generic_args()) else {
return (None, trait_params);
};
// Create pairs of the args of `self_ty` and corresponding `field_ty` to
// form the substitution list
let mut arg_substs = FxHashMap::default();
@@ -542,6 +614,8 @@ fn generate_args_for_impl(
}
}
let mut params_to_remove = FxHashSet::default();
let args = old_impl_args
.map(|old_arg| {
arg_substs.get(&old_arg.to_string()).map_or_else(
@@ -549,14 +623,18 @@ fn generate_args_for_impl(
|replace_with| {
// The old_arg will be replaced, so it becomes redundant
if trait_params.is_some() && old_trait_args.contains(&old_arg.to_string()) {
trait_params.as_ref().unwrap().remove_generic_arg(&old_arg)
params_to_remove.insert(old_arg.to_string());
}
replace_with.clone()
},
)
})
.collect_vec();
args.is_empty().not().then(|| make::generic_arg_list(args))
let make = SyntaxFactory::without_mappings();
let result = args.is_empty().not().then(|| make.generic_arg_list(args, false));
let trait_params = trait_params.and_then(|gpl| filter_generic_params(gpl, &params_to_remove));
(result, trait_params)
}
fn rename_strukt_args<N>(
@@ -570,41 +648,37 @@ fn rename_strukt_args<N>(
{
let hir_strukt = ctx.sema.to_struct_def(strukt)?;
let hir_adt = hir::Adt::from(hir_strukt);
let item = item.clone_for_update();
let scope = ctx.sema.scope(item.syntax())?;
let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone());
N::cast(transform.apply(item.syntax()))
}
fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> {
let trait_source = ctx.sema.source(trait_)?.value;
trait_source
.syntax()
.descendants_with_tokens()
.filter_map(|e| e.into_token())
.find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW)
.map(|_| ())
fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> bool {
ctx.sema
.source(trait_)
.and_then(|src| {
src.value
.syntax()
.descendants_with_tokens()
.filter_map(|e| e.into_token())
.find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW)
})
.is_some()
}
fn resolve_name_conflicts(
strukt_params: Option<ast::GenericParamList>,
old_impl_params: &Option<ast::GenericParamList>,
) -> Option<ast::GenericParamList> {
let make = SyntaxFactory::without_mappings();
match (strukt_params, old_impl_params) {
(Some(old_strukt_params), Some(old_impl_params)) => {
let params = make::generic_param_list(std::iter::empty()).clone_for_update();
let mut new_params: Vec<ast::GenericParam> = Vec::new();
for old_strukt_param in old_strukt_params.generic_params() {
// Get old name from `strukt`
let name = SmolStr::from(match &old_strukt_param {
ast::GenericParam::ConstParam(c) => c.name()?.to_string(),
ast::GenericParam::LifetimeParam(l) => {
l.lifetime()?.lifetime_ident_token()?.to_string()
}
ast::GenericParam::TypeParam(t) => t.name()?.to_string(),
});
let name = SmolStr::from(generic_param_name(&old_strukt_param)?);
// The new name cannot be conflicted with generics in trait, and the renamed names.
let param_list_to_names = |param_list: &GenericParamList| {
@@ -613,8 +687,9 @@ fn resolve_name_conflicts(
p => Some(p.to_string()),
})
};
let new_params_list = make.generic_param_list(new_params.clone());
let existing_names = param_list_to_names(old_impl_params)
.chain(param_list_to_names(&params))
.chain(param_list_to_names(&new_params_list))
.collect_vec();
let mut name_generator = suggest_name::NameGenerator::new_with_names(
existing_names.iter().map(|s| s.as_str()),
@@ -623,25 +698,21 @@ fn resolve_name_conflicts(
match old_strukt_param {
ast::GenericParam::ConstParam(c) => {
if let Some(const_ty) = c.ty() {
let const_param = make::const_param(make::name(&name), const_ty);
params.add_generic_param(ast::GenericParam::ConstParam(
const_param.clone_for_update(),
));
let const_param = make.const_param(make.name(&name), const_ty);
new_params.push(ast::GenericParam::ConstParam(const_param));
}
}
p @ ast::GenericParam::LifetimeParam(_) => {
params.add_generic_param(p.clone_for_update());
new_params.push(p.clone_for_update());
}
ast::GenericParam::TypeParam(t) => {
let type_bounds = t.type_bound_list();
let type_param = make::type_param(make::name(&name), type_bounds);
params.add_generic_param(ast::GenericParam::TypeParam(
type_param.clone_for_update(),
));
let type_param = make.type_param(make.name(&name), type_bounds);
new_params.push(ast::GenericParam::TypeParam(type_param));
}
}
}
Some(params)
Some(make.generic_param_list(new_params))
}
(Some(old_strukt_gpl), None) => Some(old_strukt_gpl),
_ => None,
@@ -666,7 +737,8 @@ fn process_assoc_item(
}
fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option<AssocItem> {
let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
let make = SyntaxFactory::without_mappings();
let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str());
// We want rhs of the const assignment to be a qualified path
// The general case for const assignment can be found [here](`https://doc.rust-lang.org/reference/items/constant-items.html`)
@@ -674,15 +746,14 @@ fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option
// <Base as Trait<GenArgs>>::ConstName;
// FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it.
// make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap());
let qualified_path = qualified_path(qual_path_ty, path_expr_segment);
let inner = make::item_const(
let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}"));
let inner = make.item_const(
item.attrs(),
item.visibility(),
item.name()?,
item.ty()?,
make::expr_path(qualified_path),
)
.clone_for_update();
make.expr_path(qualified_path),
);
Some(AssocItem::Const(inner))
}
@@ -692,59 +763,46 @@ fn func_assoc_item(
qual_path_ty: Path,
base_name: &str,
) -> Option<AssocItem> {
let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
let qualified_path = qualified_path(qual_path_ty, path_expr_segment);
let make = SyntaxFactory::without_mappings();
let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str());
let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}"));
let call = match item.param_list() {
// Methods and funcs should be handled separately.
// We ask if the func has a `self` param.
Some(l) => match l.self_param() {
Some(slf) => {
let mut self_kw = make::expr_path(make::path_from_text("self"));
self_kw = make::expr_field(self_kw, base_name);
let self_kw = make.expr_path(make.path_from_text("self"));
let self_kw = make.expr_field(self_kw, base_name).into();
let tail_expr_self = match slf.kind() {
ast::SelfParamKind::Owned => self_kw,
ast::SelfParamKind::Ref => make::expr_ref(self_kw, false),
ast::SelfParamKind::MutRef => make::expr_ref(self_kw, true),
ast::SelfParamKind::Ref => make.expr_ref(self_kw, false),
ast::SelfParamKind::MutRef => make.expr_ref(self_kw, true),
};
let param_count = l.params().count();
let args = convert_param_list_to_arg_list(l).clone_for_update();
let pos_after_l_paren = Position::after(args.l_paren_token()?);
if param_count > 0 {
// Add SelfParam and a TOKEN::COMMA
ted::insert_all_raw(
pos_after_l_paren,
vec![
NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()),
NodeOrToken::Token(make::token(SyntaxKind::COMMA)),
NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)),
],
);
} else {
// Add SelfParam only
ted::insert_raw(
pos_after_l_paren,
NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()),
);
}
// Build argument list with self expression prepended
let other_args = convert_param_list_to_arg_list(l);
let all_args: Vec<ast::Expr> =
std::iter::once(tail_expr_self).chain(other_args.args()).collect();
let args = make.arg_list(all_args);
make::expr_call(make::expr_path(qualified_path), args)
}
None => {
make::expr_call(make::expr_path(qualified_path), convert_param_list_to_arg_list(l))
make.expr_call(make.expr_path(qualified_path), args).into()
}
None => make
.expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l))
.into(),
},
None => make::expr_call(
make::expr_path(qualified_path),
convert_param_list_to_arg_list(make::param_list(None, Vec::new())),
),
}
.clone_for_update();
None => make
.expr_call(
make.expr_path(qualified_path),
convert_param_list_to_arg_list(make.param_list(None, Vec::new())),
)
.into(),
};
let body = make::block_expr(vec![], Some(call.into())).clone_for_update();
let func = make::fn_(
let body = make.block_expr(vec![], Some(call));
let func = make.fn_(
item.attrs(),
item.visibility(),
item.name()?,
@@ -757,35 +815,32 @@ fn func_assoc_item(
item.const_token().is_some(),
item.unsafe_token().is_some(),
item.gen_token().is_some(),
)
.clone_for_update();
);
Some(AssocItem::Fn(func.indent(edit::IndentLevel(1))))
}
fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option<AssocItem> {
let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
let qualified_path = qualified_path(qual_path_ty, path_expr_segment);
let ty = make::ty_path(qualified_path);
let make = SyntaxFactory::without_mappings();
let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str());
let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}"));
let ty = make.ty_path(qualified_path).into();
let ident = item.name()?.to_string();
let alias = make::ty_alias(
item.attrs(),
ident.as_str(),
item.generic_param_list(),
None,
item.where_clause(),
Some((ty, None)),
)
.indent(edit::IndentLevel(1));
let alias = make
.ty_alias(
item.attrs(),
ident.as_str(),
item.generic_param_list(),
None,
item.where_clause(),
Some((ty, None)),
)
.indent(edit::IndentLevel(1));
Some(AssocItem::TypeAlias(alias))
}
fn qualified_path(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path {
make::path_from_text(&format!("{qual_path_ty}::{path_expr_seg}"))
}
#[cfg(test)]
mod test {
@@ -269,6 +269,22 @@ fn generate_fn_alias_unnamed_generics_bounds() {
);
}
#[test]
fn generate_fn_alias_unnamed_complex_types() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o(x: Vec<i32>) {}
"#,
r#"
type ${0:FooFn} = fn(Vec<i32>);
fn foo(x: Vec<i32>) {}
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_self() {
check_assist_by_label(
@@ -405,6 +421,22 @@ fn generate_fn_alias_named_generics_bounds() {
);
}
#[test]
fn generate_fn_alias_named_complex_types() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o(x: Vec<i32>) {}
"#,
r#"
type ${0:FooFn} = fn(x: Vec<i32>);
fn foo(x: Vec<i32>) {}
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_self() {
check_assist_by_label(
@@ -1,4 +1,5 @@
use ide_db::assists::AssistId;
use hir::Semantics;
use ide_db::{RootDatabase, assists::AssistId, defs::Definition};
use syntax::{
AstNode,
ast::{self, Expr, HasArgList, make},
@@ -60,8 +61,8 @@ pub(crate) fn replace_with_lazy_method(acc: &mut Assists, ctx: &AssistContext<'_
format!("Replace {method_name} with {method_name_lazy}"),
call.syntax().text_range(),
|builder| {
let closured = into_closure(&last_arg, &method_name_lazy);
builder.replace(method_name.syntax().text_range(), method_name_lazy);
let closured = into_closure(&last_arg);
builder.replace_ast(last_arg, closured);
},
)
@@ -79,7 +80,7 @@ fn lazy_method_name(name: &str) -> String {
}
}
fn into_closure(param: &Expr) -> Expr {
fn into_closure(param: &Expr, name_lazy: &str) -> Expr {
(|| {
if let ast::Expr::CallExpr(call) = param {
if call.arg_list()?.args().count() == 0 { Some(call.expr()?) } else { None }
@@ -87,7 +88,11 @@ fn into_closure(param: &Expr) -> Expr {
None
}
})()
.unwrap_or_else(|| make::expr_closure(None, param.clone()).into())
.unwrap_or_else(|| {
let pats = (name_lazy == "and_then")
.then(|| make::untyped_param(make::ext::simple_ident_pat(make::name("it")).into()));
make::expr_closure(pats, param.clone()).into()
})
}
// Assist: replace_with_eager_method
@@ -146,21 +151,39 @@ pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<'
call.syntax().text_range(),
|builder| {
builder.replace(method_name.syntax().text_range(), method_name_eager);
let called = into_call(&last_arg);
let called = into_call(&last_arg, &ctx.sema);
builder.replace_ast(last_arg, called);
},
)
}
fn into_call(param: &Expr) -> Expr {
fn into_call(param: &Expr, sema: &Semantics<'_, RootDatabase>) -> Expr {
(|| {
if let ast::Expr::ClosureExpr(closure) = param {
if closure.param_list()?.params().count() == 0 { Some(closure.body()?) } else { None }
let mut params = closure.param_list()?.params();
match params.next() {
Some(_) if params.next().is_none() => {
let params = sema.resolve_expr_as_callable(param)?.params();
let used_param = Definition::Local(params.first()?.as_local(sema.db)?)
.usages(sema)
.at_least_one();
if used_param { None } else { Some(closure.body()?) }
}
None => Some(closure.body()?),
Some(_) => None,
}
} else {
None
}
})()
.unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())).into())
.unwrap_or_else(|| {
let callable = if needs_parens_in_call(param) {
make::expr_paren(param.clone()).into()
} else {
param.clone()
};
make::expr_call(callable, make::arg_list(Vec::new())).into()
})
}
fn eager_method_name(name: &str) -> Option<&str> {
@@ -177,6 +200,12 @@ fn ends_is(name: &str, end: &str) -> bool {
name.strip_suffix(end).is_some_and(|s| s.is_empty() || s.ends_with('_'))
}
fn needs_parens_in_call(param: &Expr) -> bool {
let call = make::expr_call(make::ext::expr_unit(), make::arg_list(Vec::new()));
let callable = call.expr().expect("invalid make call");
param.needs_parens_in_place_of(call.syntax(), callable.syntax())
}
#[cfg(test)]
mod tests {
use crate::tests::check_assist;
@@ -333,7 +362,7 @@ fn foo() {
r#"
fn foo() {
let foo = Some("foo");
return foo.and_then(|| Some("bar"));
return foo.and_then(|it| Some("bar"));
}
"#,
)
@@ -347,7 +376,7 @@ fn replace_and_then_with_and() {
//- minicore: option, fn
fn foo() {
let foo = Some("foo");
return foo.and_then$0(|| Some("bar"));
return foo.and_then$0(|it| Some("bar"));
}
"#,
r#"
@@ -359,6 +388,26 @@ fn foo() {
)
}
#[test]
fn replace_and_then_with_and_used_param() {
check_assist(
replace_with_eager_method,
r#"
//- minicore: option, fn
fn foo() {
let foo = Some("foo");
return foo.and_then$0(|it| Some(it.strip_suffix("bar")));
}
"#,
r#"
fn foo() {
let foo = Some("foo");
return foo.and((|it| Some(it.strip_suffix("bar")))());
}
"#,
)
}
#[test]
fn replace_then_some_with_then() {
check_assist(
@@ -395,6 +444,30 @@ fn foo() {
let foo = true;
let x = foo.then_some(2);
}
"#,
)
}
#[test]
fn replace_then_with_then_some_needs_parens() {
check_assist(
replace_with_eager_method,
r#"
//- minicore: option, fn, bool_impl
struct Func { f: fn() -> i32 }
fn foo() {
let foo = true;
let func = Func { f: || 2 };
let x = foo.then$0(func.f);
}
"#,
r#"
struct Func { f: fn() -> i32 }
fn foo() {
let foo = true;
let func = Func { f: || 2 };
let x = foo.then_some((func.f)());
}
"#,
)
}
@@ -1,6 +1,6 @@
use syntax::{
AstNode, AstToken,
ast::{self, HasAttrs},
ast::{self, HasAttrs, edit::AstNodeEdit},
};
use crate::{AssistContext, AssistId, Assists, utils::test_related_attribute_syn};
@@ -27,13 +27,16 @@ pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
let attr: ast::Attr = ctx.find_node_at_offset()?;
let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
let attr = test_related_attribute_syn(&func)?;
let indent = attr.indent_level();
match has_ignore_attribute(&func) {
None => acc.add(
AssistId::refactor("toggle_ignore"),
"Ignore this test",
attr.syntax().text_range(),
|builder| builder.insert(attr.syntax().text_range().end(), "\n#[ignore]"),
|builder| {
builder.insert(attr.syntax().text_range().end(), format!("\n{indent}#[ignore]"))
},
),
Some(ignore_attr) => acc.add(
AssistId::refactor("toggle_ignore"),
@@ -69,13 +72,17 @@ fn test_base_case() {
check_assist(
toggle_ignore,
r#"
#[test$0]
fn test() {}
mod indent {
#[test$0]
fn test() {}
}
"#,
r#"
#[test]
#[ignore]
fn test() {}
mod indent {
#[test]
#[ignore]
fn test() {}
}
"#,
)
}
@@ -85,13 +92,17 @@ fn test_unignore() {
check_assist(
toggle_ignore,
r#"
#[test$0]
#[ignore]
fn test() {}
mod indent {
#[test$0]
#[ignore]
fn test() {}
}
"#,
r#"
#[test]
fn test() {}
mod indent {
#[test]
fn test() {}
}
"#,
)
}
@@ -247,7 +247,6 @@ pub(crate) fn all() -> &'static [Handler] {
add_label_to_loop::add_label_to_loop,
add_lifetime_to_type::add_lifetime_to_type,
add_missing_match_arms::add_missing_match_arms,
add_return_type::add_return_type,
add_turbo_fish::add_turbo_fish,
apply_demorgan::apply_demorgan_iterator,
apply_demorgan::apply_demorgan,
@@ -392,6 +391,7 @@ pub(crate) fn all() -> &'static [Handler] {
// used as a tie-breaker.
add_missing_impl_members::add_missing_impl_members,
add_missing_impl_members::add_missing_default_members,
add_return_type::add_return_type,
//
replace_string_with_char::replace_string_with_char,
replace_string_with_char::replace_char_with_string,
@@ -27,7 +27,7 @@
make,
syntax_factory::SyntaxFactory,
},
syntax_editor::{Removable, SyntaxEditor},
syntax_editor::{Element, Removable, SyntaxEditor},
};
use crate::{
@@ -384,6 +384,28 @@ fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
}
}
pub(crate) fn insert_attributes(
before: impl Element,
edit: &mut SyntaxEditor,
attrs: impl IntoIterator<Item = ast::Attr>,
) {
let mut attrs = attrs.into_iter().peekable();
if attrs.peek().is_none() {
return;
}
let elem = before.syntax_element();
let indent = IndentLevel::from_element(&elem);
let whitespace = format!("\n{indent}");
edit.insert_all(
syntax::syntax_editor::Position::before(elem),
attrs
.flat_map(|attr| {
[attr.syntax().clone().into(), make::tokens::whitespace(&whitespace).into()]
})
.collect(),
);
}
pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
[Direction::Next, Direction::Prev].into_iter()
}
@@ -22,13 +22,8 @@ pub(crate) fn format_string(
let cursor_in_lit = cursor - lit_start;
let prefix = &original.text()[..cursor_in_lit.into()];
let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple();
let brace_offset = match braces {
// escaped brace
Some(((_, '{'), (_, '{'))) => return,
Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1),
_ => return,
};
let Some(brace_offset) = unescaped_brace(prefix) else { return };
let brace_offset = lit_start + brace_offset + TextSize::of('{');
let source_range = TextRange::new(brace_offset, cursor);
ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()).for_each(|(name, _)| {
@@ -59,6 +54,15 @@ pub(crate) fn format_string(
});
}
fn unescaped_brace(prefix: &str) -> Option<TextSize> {
let is_ident_char = |ch: char| ch.is_alphanumeric() || ch == '_';
prefix
.trim_end_matches(is_ident_char)
.strip_suffix('{')
.filter(|it| it.chars().rev().take_while(|&ch| ch == '{').count() % 2 == 0)
.map(|s| TextSize::new(s.len() as u32))
}
#[cfg(test)]
mod tests {
use expect_test::expect;
@@ -96,6 +100,82 @@ fn main() {
);
}
#[test]
fn no_completion_after_escaped() {
check_no_kw(
r#"
//- minicore: fmt
fn main() {
let foobar = 1;
format_args!("{{f$0");
}
"#,
expect![[]],
);
check_no_kw(
r#"
//- minicore: fmt
fn main() {
let foobar = 1;
format_args!("some text {{{{f$0");
}
"#,
expect![[]],
);
}
#[test]
fn completes_unescaped_after_escaped() {
check_edit(
"foobar",
r#"
//- minicore: fmt
fn main() {
let foobar = 1;
format_args!("{{{f$0");
}
"#,
r#"
fn main() {
let foobar = 1;
format_args!("{{{foobar");
}
"#,
);
check_edit(
"foobar",
r#"
//- minicore: fmt
fn main() {
let foobar = 1;
format_args!("{{{{{f$0");
}
"#,
r#"
fn main() {
let foobar = 1;
format_args!("{{{{{foobar");
}
"#,
);
check_edit(
"foobar",
r#"
//- minicore: fmt
fn main() {
let foobar = 1;
format_args!("}}{f$0");
}
"#,
r#"
fn main() {
let foobar = 1;
format_args!("}}{foobar");
}
"#,
);
}
#[test]
fn completes_locals() {
check_edit(
@@ -2,7 +2,6 @@
// It's useful to refer to code that is private in doc comments.
#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
@@ -37,6 +37,7 @@
};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use salsa::Update;
use crate::RootDatabase;
@@ -118,7 +119,7 @@ pub struct LocalRoots {
}
/// The symbol indices of modules that make up a given crate.
pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex]> {
pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex<'_>]> {
let _p = tracing::info_span!("crate_symbols").entered();
krate.modules(db).into_iter().map(|module| SymbolIndex::module_symbols(db, module)).collect()
}
@@ -148,7 +149,7 @@ pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex]>
// | Editor | Shortcut |
// |---------|-----------|
// | VS Code | <kbd>Ctrl+T</kbd>
pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol<'_>> {
let _p = tracing::info_span!("world_symbols", query = ?query.query).entered();
let indices: Vec<_> = if query.libs {
@@ -170,9 +171,7 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
crates
.par_iter()
.for_each_with(db.clone(), |snap, &krate| _ = crate_symbols(snap, krate.into()));
let indices: Vec<_> =
crates.into_iter().map(|krate| crate_symbols(db, krate.into())).collect();
indices.iter().flat_map(|indices| indices.iter().cloned()).collect()
crates.into_iter().flat_map(|krate| Vec::from(crate_symbols(db, krate.into()))).collect()
};
let mut res = vec![];
@@ -184,24 +183,27 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
}
#[derive(Default)]
pub struct SymbolIndex {
symbols: Box<[FileSymbol]>,
pub struct SymbolIndex<'db> {
symbols: Box<[FileSymbol<'db>]>,
map: fst::Map<Vec<u8>>,
}
impl SymbolIndex {
impl<'db> SymbolIndex<'db> {
/// The symbol index for a given source root within library_roots.
pub fn library_symbols(db: &dyn HirDatabase, source_root_id: SourceRootId) -> &SymbolIndex {
pub fn library_symbols(
db: &'db dyn HirDatabase,
source_root_id: SourceRootId,
) -> &'db SymbolIndex<'db> {
// FIXME:
#[salsa::interned]
struct InternedSourceRootId {
id: SourceRootId,
}
#[salsa::tracked(returns(ref))]
fn library_symbols(
db: &dyn HirDatabase,
source_root_id: InternedSourceRootId<'_>,
) -> SymbolIndex {
fn library_symbols<'db>(
db: &'db dyn HirDatabase,
source_root_id: InternedSourceRootId<'db>,
) -> SymbolIndex<'db> {
let _p = tracing::info_span!("library_symbols").entered();
// We call this without attaching because this runs in parallel, so we need to attach here.
@@ -224,7 +226,7 @@ fn library_symbols(
/// The symbol index for a given module. These modules should only be in source roots that
/// are inside local_roots.
pub fn module_symbols(db: &dyn HirDatabase, module: Module) -> &SymbolIndex {
pub fn module_symbols(db: &dyn HirDatabase, module: Module) -> &SymbolIndex<'_> {
// FIXME:
#[salsa::interned]
struct InternedModuleId {
@@ -232,7 +234,10 @@ struct InternedModuleId {
}
#[salsa::tracked(returns(ref))]
fn module_symbols(db: &dyn HirDatabase, module: InternedModuleId<'_>) -> SymbolIndex {
fn module_symbols<'db>(
db: &'db dyn HirDatabase,
module: InternedModuleId<'db>,
) -> SymbolIndex<'db> {
let _p = tracing::info_span!("module_symbols").entered();
// We call this without attaching because this runs in parallel, so we need to attach here.
@@ -250,29 +255,41 @@ fn module_symbols(db: &dyn HirDatabase, module: InternedModuleId<'_>) -> SymbolI
}
}
impl fmt::Debug for SymbolIndex {
impl fmt::Debug for SymbolIndex<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
}
}
impl PartialEq for SymbolIndex {
fn eq(&self, other: &SymbolIndex) -> bool {
impl PartialEq for SymbolIndex<'_> {
fn eq(&self, other: &SymbolIndex<'_>) -> bool {
self.symbols == other.symbols
}
}
impl Eq for SymbolIndex {}
impl Eq for SymbolIndex<'_> {}
impl Hash for SymbolIndex {
impl Hash for SymbolIndex<'_> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.symbols.hash(hasher)
}
}
impl SymbolIndex {
fn new(mut symbols: Box<[FileSymbol]>) -> SymbolIndex {
fn cmp(lhs: &FileSymbol, rhs: &FileSymbol) -> Ordering {
unsafe impl Update for SymbolIndex<'_> {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let this = unsafe { &mut *old_pointer };
if *this == new_value {
false
} else {
*this = new_value;
true
}
}
}
impl<'db> SymbolIndex<'db> {
fn new(mut symbols: Box<[FileSymbol<'db>]>) -> SymbolIndex<'db> {
fn cmp(lhs: &FileSymbol<'_>, rhs: &FileSymbol<'_>) -> Ordering {
let lhs_chars = lhs.name.as_str().chars().map(|c| c.to_ascii_lowercase());
let rhs_chars = rhs.name.as_str().chars().map(|c| c.to_ascii_lowercase());
lhs_chars.cmp(rhs_chars)
@@ -318,7 +335,7 @@ pub fn len(&self) -> usize {
}
pub fn memory_size(&self) -> usize {
self.map.as_fst().size() + self.symbols.len() * size_of::<FileSymbol>()
self.map.as_fst().size() + self.symbols.len() * size_of::<FileSymbol<'_>>()
}
fn range_to_map_value(start: usize, end: usize) -> u64 {
@@ -336,10 +353,10 @@ fn map_value_to_range(value: u64) -> (usize, usize) {
}
impl Query {
pub(crate) fn search<'sym, T>(
pub(crate) fn search<'db, T>(
self,
indices: &'sym [&SymbolIndex],
cb: impl FnMut(&'sym FileSymbol) -> ControlFlow<T>,
indices: &[&'db SymbolIndex<'db>],
cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow<T>,
) -> Option<T> {
let _p = tracing::info_span!("symbol_index::Query::search").entered();
let mut op = fst::map::OpBuilder::new();
@@ -371,11 +388,11 @@ pub(crate) fn search<'sym, T>(
}
}
fn search_maps<'sym, T>(
fn search_maps<'db, T>(
&self,
indices: &'sym [&SymbolIndex],
indices: &[&'db SymbolIndex<'db>],
mut stream: fst::map::Union<'_>,
mut cb: impl FnMut(&'sym FileSymbol) -> ControlFlow<T>,
mut cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow<T>,
) -> Option<T> {
let ignore_underscore_prefixed = !self.query.starts_with("__");
while let Some((_, indexed_values)) = stream.next() {
@@ -39,6 +39,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Struct",
@@ -73,6 +74,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "mul1",
@@ -107,6 +109,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "mul2",
@@ -141,6 +144,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "s1",
@@ -175,6 +179,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "s1",
@@ -209,6 +214,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "s2",
@@ -243,6 +249,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
],
),
@@ -39,6 +39,7 @@
is_assoc: true,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Alias",
@@ -71,6 +72,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "B",
@@ -105,6 +107,7 @@
is_assoc: true,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "CONST",
@@ -137,6 +140,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "CONST_WITH_INNER",
@@ -169,6 +173,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Enum",
@@ -203,6 +208,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "ItemLikeMacro",
@@ -237,6 +243,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Macro",
@@ -271,6 +278,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "STATIC",
@@ -303,6 +311,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Struct",
@@ -337,6 +346,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructFromMacro",
@@ -371,6 +381,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructInFn",
@@ -407,6 +418,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructInNamedConst",
@@ -443,6 +455,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructInUnnamedConst",
@@ -477,6 +490,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructT",
@@ -511,6 +525,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Trait",
@@ -543,6 +558,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Trait",
@@ -577,6 +593,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Union",
@@ -611,6 +628,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "a_mod",
@@ -643,6 +661,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "b_mod",
@@ -675,6 +694,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "define_struct",
@@ -709,6 +729,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "generic_impl_fn",
@@ -743,6 +764,7 @@
is_assoc: true,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "impl_fn",
@@ -777,6 +799,7 @@
is_assoc: true,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "macro_rules_macro",
@@ -811,6 +834,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "main",
@@ -843,6 +867,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "really_define_struct",
@@ -877,6 +902,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "trait_fn",
@@ -911,6 +937,7 @@
is_assoc: true,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
],
),
@@ -954,6 +981,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
],
),
@@ -995,6 +1023,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "IsThisJustATrait",
@@ -1029,6 +1058,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "StructInModB",
@@ -1063,6 +1093,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "SuperItemLikeMacro",
@@ -1097,6 +1128,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "ThisStruct",
@@ -1131,6 +1163,7 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
],
),
@@ -32,5 +32,6 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
]
@@ -32,6 +32,7 @@
is_assoc: false,
is_import: false,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
FileSymbol {
name: "Foo",
@@ -66,5 +67,6 @@
is_assoc: false,
is_import: true,
do_not_complete: Yes,
_marker: PhantomData<&()>,
},
]
@@ -1000,7 +1000,8 @@ fn BAD_CASE() {}
// ^^^^^^^^^^^^ 💡 error: Module `OtherBadCase` should have snake_case name, e.g. `other_bad_case`
//- /BAD_CASE/OtherBadCase.rs
#![deny(non_snake_case)]
#![allow(non_snake_case)]
#![deny(non_snake_case)] // The lint level has been overridden.
fn FOO() {}
// ^^^ 💡 error: Function `FOO` should have snake_case name, e.g. `foo`
@@ -296,8 +296,12 @@ fn no_missing_unsafe_diagnostic_with_deprecated_safe_2024() {
#[rustc_deprecated_safe_2024]
fn set_var() {}
#[rustc_deprecated_safe_2024(audit_that = "something")]
fn set_var2() {}
fn main() {
set_var();
set_var2();
}
"#,
);
@@ -995,6 +995,10 @@ fn f() {
}
"#,
);
// FIXME: There should be no "unused variable" here, and there should be a mutability error,
// but our MIR infra is horribly broken and due to the order in which expressions are lowered
// there is no `StorageLive` for `x` in the closure (in fact, `x` should not even be a variable
// of the closure, the environment should be, but as I said, our MIR infra is horribly broken).
check_diagnostics(
r#"
//- minicore: copy, fn
@@ -1003,8 +1007,8 @@ fn f() {
|| {
|| {
let x = 2;
// ^ 💡 warn: unused variable
|| { || { x = 5; } }
//^^^^^ 💡 error: cannot mutate immutable variable `x`
}
}
};
@@ -102,7 +102,8 @@ fn missing_record_expr_field_fixes(
let indent = IndentLevel::from_node(last_field_syntax);
let mut new_field = new_field.to_string();
if usage_file_id != def_file_id {
// FIXME: check submodule instead of FileId
if usage_file_id != def_file_id && !matches!(def_id, hir::VariantDef::Variant(_)) {
new_field = format!("pub(crate) {new_field}");
}
new_field = format!("\n{indent}{new_field}");
@@ -357,6 +358,34 @@ pub struct Foo {
)
}
#[test]
fn test_add_enum_variant_field_in_other_file_from_usage() {
check_fix(
r#"
//- /main.rs
mod foo;
fn main() {
foo::Foo::Variant { bar: 3, $0baz: false};
}
//- /foo.rs
pub enum Foo {
Variant {
bar: i32
}
}
"#,
r#"
pub enum Foo {
Variant {
bar: i32,
baz: bool
}
}
"#,
)
}
#[test]
fn test_tuple_field_on_record_struct() {
check_no_fix(
@@ -182,6 +182,61 @@ fn main2() {
);
}
#[test]
fn apply_last_lint_attribute_when_multiple_are_present() {
check_diagnostics(
r#"
#![allow(unused_variables)]
#![warn(unused_variables)]
#![deny(unused_variables)]
fn main() {
let x = 2;
//^ 💡 error: unused variable
#[deny(unused_variables)]
#[warn(unused_variables)]
#[allow(unused_variables)]
let y = 0;
}
"#,
);
}
#[test]
fn prefer_closest_ancestor_lint_attribute() {
check_diagnostics(
r#"
#![allow(unused_variables)]
fn main() {
#![warn(unused_variables)]
#[deny(unused_variables)]
let x = 2;
//^ 💡 error: unused variable
}
#[warn(unused_variables)]
fn main2() {
#[deny(unused_variables)]
let x = 2;
//^ 💡 error: unused variable
}
#[warn(unused_variables)]
fn main3() {
let x = 2;
//^ 💡 warn: unused variable
}
fn main4() {
let x = 2;
}
"#,
);
}
#[test]
fn fix_unused_variable() {
check_fix(
@@ -648,19 +648,13 @@ fn find_outline_mod_lint_severity(
let mod_def = sema.to_module_def(&mod_node)?;
let module_source_file = sema.module_definition_node(mod_def);
let mut result = None;
let lint_groups = lint_groups(&diag.code, edition);
lint_attrs(
sema,
krate,
ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
)
.for_each(|(lint, severity)| {
if lint_groups.contains(&lint) {
result = Some(severity);
}
});
result
.find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity))
}
fn lint_severity_at(
@@ -687,7 +681,7 @@ fn lint_attrs(
krate: hir::Crate,
ancestor: ast::AnyHasAttrs,
) -> impl Iterator<Item = (SmolStr, Severity)> {
sema.lint_attrs(krate, ancestor).map(|(lint_attr, lint)| {
sema.lint_attrs(krate, ancestor).rev().map(|(lint_attr, lint)| {
let severity = match lint_attr {
hir::LintAttr::Allow | hir::LintAttr::Expect => Severity::Allow,
hir::LintAttr::Warn => Severity::Warning,
@@ -11169,3 +11169,60 @@ fn foo() {
"#]],
);
}
#[test]
fn hover_trait_impl_shows_generic_args() {
// Single generic arg
check(
r#"
trait Foo<T> {
fn foo(&self) {}
}
impl<T> Foo<()> for T {
fn fo$0o(&self) {}
}
fn bar() {
().foo();
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture
```
```rust
impl<T> Foo<()> for T
fn foo(&self)
```
"#]],
);
// Multiple generic args
check(
r#"
trait Foo<A, B> {
fn foo(&self) {}
}
impl<T> Foo<i32, u64> for T {
fn fo$0o(&self) {}
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture
```
```rust
impl<T> Foo<i32, u64> for T
fn foo(&self)
```
"#]],
);
}
@@ -225,7 +225,7 @@ pub(crate) fn from_syntax(
}
}
impl TryToNav for FileSymbol {
impl<'db> TryToNav for FileSymbol<'db> {
fn try_to_nav(
&self,
sema: &Semantics<'_, RootDatabase>,
@@ -10,7 +10,7 @@
documentation::Documentation,
famous_defs::FamousDefs,
};
use syntax::{AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T, TextRange};
use syntax::{AstNode, SyntaxNode, SyntaxToken, TextRange};
use crate::navigation_target::UpmappingResult;
use crate::{
@@ -136,12 +136,12 @@ fn documentation_for_definition(
}
// FIXME: This is a weird function
fn get_definitions(
sema: &Semantics<'_, RootDatabase>,
fn get_definitions<'db>(
sema: &Semantics<'db, RootDatabase>,
token: SyntaxToken,
) -> Option<ArrayVec<Definition, 2>> {
) -> Option<ArrayVec<(Definition, Option<hir::GenericSubstitution<'db>>), 2>> {
for token in sema.descend_into_macros_exact(token) {
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions);
if let Some(defs) = def
&& !defs.is_empty()
{
@@ -226,12 +226,6 @@ fn add_file(&mut self, file_id: FileId) {
show_drop_glue: true,
minicore: MiniCore::default(),
};
let tokens = tokens.filter(|token| {
matches!(
token.kind(),
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self]
)
});
let mut result = StaticIndexedFile { file_id, inlay_hints, folds, tokens: vec![] };
let mut add_token = |def: Definition, range: TextRange, scope_node: &SyntaxNode| {
@@ -291,9 +285,9 @@ fn add_file(&mut self, file_id: FileId) {
let range = token.text_range();
let node = token.parent().unwrap();
match hir::attach_db(self.db, || get_definitions(&sema, token.clone())) {
Some(it) => {
for i in it {
add_token(i, range, &node);
Some(defs) => {
for (def, _) in defs {
add_token(def, range, &node);
}
}
None => continue,
@@ -24,9 +24,9 @@
#[cfg(not(feature = "in-rust-tree"))]
extern crate ra_ap_rustc_lexer as rustc_lexer;
#[cfg(feature = "in-rust-tree")]
extern crate rustc_lexer;
#[cfg(feature = "in-rust-tree")]
extern crate rustc_driver as _;
#[cfg(feature = "in-rust-tree")]
extern crate rustc_lexer;
mod event;
mod frontmatter;
@@ -11,7 +11,6 @@
feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)
)]
#![allow(internal_features)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
@@ -17,7 +17,6 @@
// It's useful to refer to code that is private in doc comments.
#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
@@ -603,6 +603,29 @@ pub fn func() {}
);
}
#[test]
fn operator_overload() {
check_symbol(
r#"
//- minicore: add
//- /workspace/lib.rs crate:main
use core::ops::AddAssign;
struct S;
impl AddAssign for S {
fn add_assign(&mut self, _rhs: Self) {}
}
fn main() {
let mut s = S;
s +=$0 S;
}
"#,
"rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",
);
}
#[test]
fn symbol_for_trait() {
check_symbol(
@@ -19,7 +19,7 @@
/// Any toolchain less than this version will likely not work with rust-analyzer built from this revision.
pub const MINIMUM_SUPPORTED_TOOLCHAIN_VERSION: semver::Version = semver::Version {
major: 1,
minor: 90,
minor: 78,
patch: 0,
pre: semver::Prerelease::EMPTY,
build: semver::BuildMetadata::EMPTY,
@@ -37,7 +37,11 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
change: Some(TextDocumentSyncKind::INCREMENTAL),
will_save: None,
will_save_wait_until: None,
save: Some(SaveOptions::default().into()),
save: if config.caps().did_save_text_document_dynamic_registration() {
None
} else {
Some(SaveOptions::default().into())
},
})),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
@@ -437,11 +437,17 @@ fn handle_event(&mut self, event: Event) {
}
}
Event::Flycheck(message) => {
let _p = tracing::info_span!("GlobalState::handle_event/flycheck").entered();
self.handle_flycheck_msg(message);
let mut cargo_finished = false;
self.handle_flycheck_msg(message, &mut cargo_finished);
// Coalesce many flycheck updates into a single loop turn
while let Ok(message) = self.flycheck_receiver.try_recv() {
self.handle_flycheck_msg(message);
self.handle_flycheck_msg(message, &mut cargo_finished);
}
if cargo_finished {
self.send_request::<lsp_types::request::WorkspaceDiagnosticRefresh>(
(),
|_, _| (),
);
}
}
Event::TestResult(message) => {
@@ -1109,7 +1115,7 @@ fn handle_cargo_test_msg(&mut self, message: CargoTestMessage) {
}
}
fn handle_flycheck_msg(&mut self, message: FlycheckMessage) {
fn handle_flycheck_msg(&mut self, message: FlycheckMessage, cargo_finished: &mut bool) {
match message {
FlycheckMessage::AddDiagnostic {
id,
@@ -1167,6 +1173,7 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage) {
flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
flycheck::Progress::DidCancel => {
self.last_flycheck_error = None;
*cargo_finished = true;
(Progress::End, None)
}
flycheck::Progress::DidFailToRestart(err) => {
@@ -1177,6 +1184,7 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage) {
flycheck::Progress::DidFinish(result) => {
self.last_flycheck_error =
result.err().map(|err| format!("cargo check failed to start: {err}"));
*cargo_finished = true;
(Progress::End, None)
}
};
@@ -48,6 +48,7 @@ pub(crate) fn with_fixture(fixture: &str) -> Project<'_> {
"enable": false,
},
},
"checkOnSave": false,
"procMacro": {
"enable": false,
}
@@ -658,7 +658,7 @@ pub fn expr_if(
};
expr_from_text(&format!("if {condition} {then_branch} {else_branch}"))
}
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr {
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::ForExpr {
expr_from_text(&format!("for {pat} in {expr} {block}"))
}
@@ -1016,7 +1016,19 @@ pub fn item_static(
}
pub fn unnamed_param(ty: ast::Type) -> ast::Param {
ast_from_text(&format!("fn f({ty}) {{ }}"))
quote! {
Param {
#ty
}
}
}
pub fn untyped_param(pat: ast::Pat) -> ast::Param {
quote! {
Param {
#pat
}
}
}
pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
@@ -1456,3 +1468,86 @@ pub fn ws(&self) -> SyntaxToken {
}
}
}
#[cfg(test)]
mod tests {
use expect_test::expect;
use super::*;
#[track_caller]
fn check(node: impl AstNode, expect: expect_test::Expect) {
let node_debug = format!("{:#?}", node.syntax());
expect.assert_eq(&node_debug);
}
#[test]
fn test_unnamed_param() {
check(
unnamed_param(ty("Vec")),
expect![[r#"
PARAM@0..3
PATH_TYPE@0..3
PATH@0..3
PATH_SEGMENT@0..3
NAME_REF@0..3
IDENT@0..3 "Vec"
"#]],
);
check(
unnamed_param(ty("Vec<T>")),
expect![[r#"
PARAM@0..6
PATH_TYPE@0..6
PATH@0..6
PATH_SEGMENT@0..6
NAME_REF@0..3
IDENT@0..3 "Vec"
GENERIC_ARG_LIST@3..6
L_ANGLE@3..4 "<"
TYPE_ARG@4..5
PATH_TYPE@4..5
PATH@4..5
PATH_SEGMENT@4..5
NAME_REF@4..5
IDENT@4..5 "T"
R_ANGLE@5..6 ">"
"#]],
);
}
#[test]
fn test_untyped_param() {
check(
untyped_param(path_pat(ext::ident_path("name"))),
expect![[r#"
PARAM@0..4
IDENT_PAT@0..4
NAME@0..4
IDENT@0..4 "name"
"#]],
);
check(
untyped_param(
range_pat(
Some(path_pat(ext::ident_path("start"))),
Some(path_pat(ext::ident_path("end"))),
)
.into(),
),
expect![[r#"
PARAM@0..10
RANGE_PAT@0..10
IDENT_PAT@0..5
NAME@0..5
IDENT@0..5 "start"
DOT2@5..7 ".."
IDENT_PAT@7..10
NAME@7..10
IDENT@7..10 "end"
"#]],
);
}
}
@@ -813,13 +813,16 @@ pub enum TypeBoundKind {
}
impl ast::TypeBound {
pub fn kind(&self) -> TypeBoundKind {
pub fn kind(&self) -> Option<TypeBoundKind> {
if let Some(path_type) = support::children(self.syntax()).next() {
TypeBoundKind::PathType(self.for_binder(), path_type)
Some(TypeBoundKind::PathType(self.for_binder(), path_type))
} else if let Some(for_binder) = support::children::<ast::ForType>(&self.syntax).next() {
let Some(ast::Type::PathType(path_type)) = for_binder.ty() else { return None };
Some(TypeBoundKind::PathType(for_binder.for_binder(), path_type))
} else if let Some(args) = self.use_bound_generic_args() {
TypeBoundKind::Use(args)
Some(TypeBoundKind::Use(args))
} else if let Some(lifetime) = self.lifetime() {
TypeBoundKind::Lifetime(lifetime)
Some(TypeBoundKind::Lifetime(lifetime))
} else {
unreachable!()
}
@@ -71,6 +71,188 @@ pub fn type_param(
ast
}
pub fn path_from_text(&self, text: &str) -> ast::Path {
make::path_from_text(text).clone_for_update()
}
pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr {
let ast::Expr::FieldExpr(ast) =
make::expr_field(receiver.clone(), field).clone_for_update()
else {
unreachable!()
};
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_node(receiver.syntax().clone(), ast.expr().unwrap().syntax().clone());
builder.finish(&mut mapping);
}
ast
}
pub fn impl_trait(
&self,
attrs: impl IntoIterator<Item = ast::Attr>,
is_unsafe: bool,
trait_gen_params: Option<ast::GenericParamList>,
trait_gen_args: Option<ast::GenericArgList>,
type_gen_params: Option<ast::GenericParamList>,
type_gen_args: Option<ast::GenericArgList>,
is_negative: bool,
path_type: ast::Type,
ty: ast::Type,
trait_where_clause: Option<ast::WhereClause>,
ty_where_clause: Option<ast::WhereClause>,
body: Option<ast::AssocItemList>,
) -> ast::Impl {
let (attrs, attrs_input) = iterator_input(attrs);
let ast = make::impl_trait(
attrs,
is_unsafe,
trait_gen_params.clone(),
trait_gen_args.clone(),
type_gen_params.clone(),
type_gen_args.clone(),
is_negative,
path_type.clone(),
ty.clone(),
trait_where_clause.clone(),
ty_where_clause.clone(),
body.clone(),
)
.clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone()));
if let Some(trait_gen_params) = trait_gen_params {
builder.map_node(
trait_gen_params.syntax().clone(),
ast.generic_param_list().unwrap().syntax().clone(),
);
}
builder.map_node(path_type.syntax().clone(), ast.trait_().unwrap().syntax().clone());
builder.map_node(ty.syntax().clone(), ast.self_ty().unwrap().syntax().clone());
if let Some(ty_where_clause) = ty_where_clause {
builder.map_node(
ty_where_clause.syntax().clone(),
ast.where_clause().unwrap().syntax().clone(),
);
}
if let Some(body) = body {
builder.map_node(
body.syntax().clone(),
ast.assoc_item_list().unwrap().syntax().clone(),
);
}
builder.finish(&mut mapping);
}
ast
}
pub fn ty_alias(
&self,
attrs: impl IntoIterator<Item = ast::Attr>,
ident: &str,
generic_param_list: Option<ast::GenericParamList>,
type_param_bounds: Option<ast::TypeParam>,
where_clause: Option<ast::WhereClause>,
assignment: Option<(ast::Type, Option<ast::WhereClause>)>,
) -> ast::TypeAlias {
let (attrs, attrs_input) = iterator_input(attrs);
let ast = make::ty_alias(
attrs,
ident,
generic_param_list.clone(),
type_param_bounds.clone(),
where_clause.clone(),
assignment.clone(),
)
.clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone()));
if let Some(generic_param_list) = generic_param_list {
builder.map_node(
generic_param_list.syntax().clone(),
ast.generic_param_list().unwrap().syntax().clone(),
);
}
if let Some(type_param_bounds) = type_param_bounds {
builder.map_node(
type_param_bounds.syntax().clone(),
ast.type_bound_list().unwrap().syntax().clone(),
);
}
if let Some(where_clause) = where_clause {
builder.map_node(
where_clause.syntax().clone(),
ast.where_clause().unwrap().syntax().clone(),
);
}
if let Some((ty, _)) = assignment {
builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
}
builder.finish(&mut mapping);
}
ast
}
pub fn param_list(
&self,
self_param: Option<ast::SelfParam>,
params: impl IntoIterator<Item = ast::Param>,
) -> ast::ParamList {
let (params, input) = iterator_input(params);
let ast = make::param_list(self_param.clone(), params).clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
if let Some(self_param) = self_param
&& let Some(new_self_param) = ast.self_param()
{
builder.map_node(self_param.syntax().clone(), new_self_param.syntax().clone());
}
builder.map_children(input, ast.params().map(|p| p.syntax().clone()));
builder.finish(&mut mapping);
}
ast
}
pub fn const_param(&self, name: ast::Name, ty: ast::Type) -> ast::ConstParam {
let ast = make::const_param(name.clone(), ty.clone()).clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
builder.finish(&mut mapping);
}
ast
}
pub fn generic_param_list(
&self,
params: impl IntoIterator<Item = ast::GenericParam>,
) -> ast::GenericParamList {
let (params, input) = iterator_input(params);
let ast = make::generic_param_list(params).clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_children(input, ast.generic_params().map(|p| p.syntax().clone()));
builder.finish(&mut mapping);
}
ast
}
pub fn path_segment(&self, name_ref: ast::NameRef) -> ast::PathSegment {
let ast = make::path_segment(name_ref.clone()).clone_for_update();
@@ -671,6 +853,26 @@ pub fn expr_while_loop(&self, condition: ast::Expr, body: ast::BlockExpr) -> ast
ast
}
pub fn expr_for_loop(
&self,
pat: ast::Pat,
iterable: ast::Expr,
body: ast::BlockExpr,
) -> ast::ForExpr {
let ast =
make::expr_for_loop(pat.clone(), iterable.clone(), body.clone()).clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_node(pat.syntax().clone(), ast.pat().unwrap().syntax().clone());
builder.map_node(iterable.syntax().clone(), ast.iterable().unwrap().syntax().clone());
builder.map_node(body.syntax().clone(), ast.loop_body().unwrap().syntax().clone());
builder.finish(&mut mapping);
}
ast
}
pub fn expr_let(&self, pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr {
let ast = make::expr_let(pattern.clone(), expr.clone()).clone_for_update();
@@ -1272,6 +1474,23 @@ pub fn fn_(
ast
}
pub fn assoc_item_list(
&self,
items: impl IntoIterator<Item = ast::AssocItem>,
) -> ast::AssocItemList {
let (items, input) = iterator_input(items);
let items_vec: Vec<_> = items.into_iter().collect();
let ast = make::assoc_item_list(Some(items_vec)).clone_for_update();
if let Some(mut mapping) = self.mappings() {
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
builder.map_children(input, ast.assoc_items().map(|item| item.syntax().clone()));
builder.finish(&mut mapping);
}
ast
}
pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr {
let ast = make::attr_outer(meta.clone()).clone_for_update();
@@ -34,7 +34,8 @@
//! eq: sized
//! error: fmt
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
//! fmt_before_1_89_0: fmt
//! fmt_before_1_93_0: fmt
//! fmt_before_1_89_0: fmt_before_1_93_0
//! fn: sized, tuple
//! from: sized, result
//! future: pin
@@ -1259,6 +1260,7 @@ pub enum Alignment {
Unknown,
}
// region:fmt_before_1_93_0
#[lang = "format_count"]
pub enum Count {
Is(usize),
@@ -1288,6 +1290,7 @@ pub const fn new(
Placeholder { position, fill, align, flags, precision, width }
}
}
// endregion:fmt_before_1_93_0
// region:fmt_before_1_89_0
#[lang = "format_unsafe_arg"]
@@ -1303,6 +1306,7 @@ pub unsafe fn new() -> Self {
// endregion:fmt_before_1_89_0
}
// region:fmt_before_1_93_0
#[derive(Copy, Clone)]
#[lang = "format_arguments"]
pub struct Arguments<'a> {
@@ -1341,6 +1345,14 @@ pub unsafe fn new_v1_formatted(
}
// endregion:!fmt_before_1_89_0
pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
Self::from_str(s)
}
pub const fn from_str(s: &'static str) -> Arguments<'a> {
Arguments { pieces: &[s], fmt: None, args: &[] }
}
pub const fn as_str(&self) -> Option<&'static str> {
match (self.pieces, self.args) {
([], []) => Some(""),
@@ -1349,6 +1361,41 @@ pub const fn as_str(&self) -> Option<&'static str> {
}
}
}
// endregion:fmt_before_1_93_0
// region:!fmt_before_1_93_0
#[lang = "format_arguments"]
#[derive(Copy, Clone)]
pub struct Arguments<'a> {
// This is a non-faithful representation of `core::fmt::Arguments`, because the real one
// is too complex for minicore.
message: Option<&'a str>,
}
impl<'a> Arguments<'a> {
pub unsafe fn new<const N: usize, const M: usize>(
_template: &'a [u8; N],
_args: &'a [rt::Argument<'a>; M],
) -> Arguments<'a> {
Arguments { message: None }
}
pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
Arguments { message: Some(s) }
}
pub const fn from_str(s: &'static str) -> Arguments<'a> {
Arguments { message: Some(s) }
}
pub fn as_str(&self) -> Option<&'static str> {
match self.message {
Some(s) => unsafe { Some(&*(s as *const str)) },
None => None,
}
}
}
// endregion:!fmt_before_1_93_0
// region:derive
pub(crate) mod derive {
@@ -1817,7 +1864,7 @@ pub const fn panic_fmt(fmt: crate::fmt::Arguments<'_>) -> ! {
#[lang = "panic"]
pub const fn panic(expr: &'static str) -> ! {
panic_fmt(crate::fmt::Arguments::new_const(&[expr]))
panic_fmt(crate::fmt::Arguments::from_str(expr))
}
}
// endregion:panic
+22 -16
View File
@@ -1486,6 +1486,7 @@
"integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.25.0",
"@typescript-eslint/types": "8.25.0",
@@ -1869,6 +1870,7 @@
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2838,6 +2840,7 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3319,6 +3322,7 @@
"integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4406,6 +4410,7 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -4508,25 +4513,25 @@
}
},
"node_modules/jsonwebtoken/node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jsonwebtoken/node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
"integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
"dev": true,
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
}
},
@@ -4544,25 +4549,25 @@
}
},
"node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"dev": true,
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -6673,6 +6678,7 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
+1 -1
View File
@@ -1 +1 @@
dfe1b8c97bcde283102f706d5dcdc3649e5e12e3
0208ee09be465f69005a7a12c28d5eccac7d5f34
-3
View File
@@ -25,6 +25,3 @@ labels = ["has-merge-commits", "S-waiting-on-author"]
# Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree
[canonicalize-issue-links]
# Prevents mentions in commits to avoid users being spammed
[no-mentions]