mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Auto merge of #141954 - matthiaskrgr:rollup-zptd6t9, r=matthiaskrgr
Rollup of 9 pull requests Successful merges: - rust-lang/rust#141554 (Improve documentation for codegen options) - rust-lang/rust#141817 (rustc_llvm: add Windows system libs only when cross-compiling from Wi…) - rust-lang/rust#141843 (Add `visit_id` to ast `Visitor`) - rust-lang/rust#141881 (Subtree update of `rust-analyzer`) - rust-lang/rust#141898 ([rustdoc-json] Implement PartialOrd and Ord for rustdoc_types::Id) - rust-lang/rust#141921 (Disable f64 minimum/maximum tests for arm 32) - rust-lang/rust#141930 (Enable triagebot `[concern]` functionality) - rust-lang/rust#141936 (Decouple "reporting in deps" from `FutureIncompatibilityReason`) - rust-lang/rust#141949 (move `test-float-parse` tool into `src/tools` dir) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
96ac9469b1
@ -42,6 +42,49 @@ impl FilePosition {
|
|||||||
FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset }
|
FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FileRange> for HirFileRange {
|
||||||
|
fn from(value: FileRange) -> Self {
|
||||||
|
HirFileRange { file_id: value.file_id.into(), range: value.range }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FilePosition> for HirFilePosition {
|
||||||
|
fn from(value: FilePosition) -> Self {
|
||||||
|
HirFilePosition { file_id: value.file_id.into(), offset: value.offset }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilePositionWrapper<span::FileId> {
|
||||||
|
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FilePosition {
|
||||||
|
FilePositionWrapper {
|
||||||
|
file_id: EditionedFileId::new(db, self.file_id, edition),
|
||||||
|
offset: self.offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileRangeWrapper<span::FileId> {
|
||||||
|
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FileRange {
|
||||||
|
FileRangeWrapper {
|
||||||
|
file_id: EditionedFileId::new(db, self.file_id, edition),
|
||||||
|
range: self.range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InFileWrapper<span::FileId, T> {
|
||||||
|
pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile<T> {
|
||||||
|
InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HirFileRange {
|
||||||
|
pub fn file_range(self) -> Option<FileRange> {
|
||||||
|
Some(FileRange { file_id: self.file_id.file_id()?, range: self.range })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub struct FileRangeWrapper<FileKind> {
|
pub struct FileRangeWrapper<FileKind> {
|
||||||
pub file_id: FileKind,
|
pub file_id: FileKind,
|
||||||
@ -194,6 +237,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
|
|||||||
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
|
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
|
||||||
self.with_value(self.value.syntax())
|
self.with_value(self.value.syntax())
|
||||||
}
|
}
|
||||||
|
pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
|
||||||
|
FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
|
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
|
||||||
@ -204,9 +250,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// region:specific impls
|
// region:specific impls
|
||||||
impl<SN: Borrow<SyntaxNode>> InRealFile<SN> {
|
impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
|
||||||
pub fn file_range(&self) -> FileRange {
|
pub fn file_range(&self) -> FileRangeWrapper<FileId> {
|
||||||
FileRange { file_id: self.file_id, range: self.value.borrow().text_range() }
|
FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +392,10 @@ impl HirFileId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn call_node(self, db: &dyn ExpandDatabase) -> Option<InFile<SyntaxNode>> {
|
||||||
|
Some(db.lookup_intern_macro_call(self.macro_file()?).to_node(db))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_builtin_derive_attr_node(
|
pub fn as_builtin_derive_attr_node(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn ExpandDatabase,
|
db: &dyn ExpandDatabase,
|
||||||
@ -848,7 +852,10 @@ impl ExpansionInfo {
|
|||||||
map_node_range_up(db, &self.exp_map, range)
|
map_node_range_up(db, &self.exp_map, range)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps up the text range out of the expansion into is macro call.
|
/// Maps up the text range out of the expansion into its macro call.
|
||||||
|
///
|
||||||
|
/// Note that this may return multiple ranges as we lose the precise association between input to output
|
||||||
|
/// and as such we may consider inputs that are unrelated.
|
||||||
pub fn map_range_up_once(
|
pub fn map_range_up_once(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn ExpandDatabase,
|
db: &dyn ExpandDatabase,
|
||||||
@ -864,11 +871,10 @@ impl ExpansionInfo {
|
|||||||
InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] }
|
InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] }
|
||||||
}
|
}
|
||||||
SpanMap::ExpansionSpanMap(arg_map) => {
|
SpanMap::ExpansionSpanMap(arg_map) => {
|
||||||
let arg_range = self
|
let Some(arg_node) = &self.arg.value else {
|
||||||
.arg
|
return InFile::new(self.arg.file_id, smallvec::smallvec![]);
|
||||||
.value
|
};
|
||||||
.as_ref()
|
let arg_range = arg_node.text_range();
|
||||||
.map_or_else(|| TextRange::empty(TextSize::from(0)), |it| it.text_range());
|
|
||||||
InFile::new(
|
InFile::new(
|
||||||
self.arg.file_id,
|
self.arg.file_id,
|
||||||
arg_map
|
arg_map
|
||||||
|
@ -20,42 +20,46 @@ pub fn prettify_macro_expansion(
|
|||||||
let span_offset = syn.text_range().start();
|
let span_offset = syn.text_range().start();
|
||||||
let target_crate = target_crate_id.data(db);
|
let target_crate = target_crate_id.data(db);
|
||||||
let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
|
let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
|
||||||
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| {
|
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
|
||||||
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
|
syn,
|
||||||
let replacement =
|
&mut |dollar_crate| {
|
||||||
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
|
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
|
||||||
let macro_call_id =
|
let replacement =
|
||||||
ctx.outer_expn(db).expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
|
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
|
||||||
let macro_call = db.lookup_intern_macro_call(macro_call_id.into());
|
let macro_call_id = ctx
|
||||||
let macro_def_crate = macro_call.def.krate;
|
.outer_expn(db)
|
||||||
// First, if this is the same crate as the macro, nothing will work but `crate`.
|
.expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
|
||||||
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
|
let macro_call = db.lookup_intern_macro_call(macro_call_id.into());
|
||||||
// will work in inserted code and match the user's expectation.
|
let macro_def_crate = macro_call.def.krate;
|
||||||
// If not, the crate's display name is what the dependency name is likely to be once such dependency
|
// First, if this is the same crate as the macro, nothing will work but `crate`.
|
||||||
// is inserted, and also understandable to the user.
|
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
|
||||||
// Lastly, if nothing else found, resort to leaving `$crate`.
|
// will work in inserted code and match the user's expectation.
|
||||||
if target_crate_id == macro_def_crate {
|
// If not, the crate's display name is what the dependency name is likely to be once such dependency
|
||||||
make::tokens::crate_kw()
|
// is inserted, and also understandable to the user.
|
||||||
} else if let Some(dep) =
|
// Lastly, if nothing else found, resort to leaving `$crate`.
|
||||||
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
|
if target_crate_id == macro_def_crate {
|
||||||
{
|
make::tokens::crate_kw()
|
||||||
make::tokens::ident(dep.name.as_str())
|
} else if let Some(dep) =
|
||||||
} else if let Some(crate_name) = ¯o_def_crate.extra_data(db).display_name {
|
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
|
||||||
make::tokens::ident(crate_name.crate_name().as_str())
|
{
|
||||||
} else {
|
make::tokens::ident(dep.name.as_str())
|
||||||
return dollar_crate.clone();
|
} else if let Some(crate_name) = ¯o_def_crate.extra_data(db).display_name {
|
||||||
}
|
make::tokens::ident(crate_name.crate_name().as_str())
|
||||||
});
|
} else {
|
||||||
if replacement.text() == "$crate" {
|
return dollar_crate.clone();
|
||||||
// The parent may have many children, and looking for the token may yield incorrect results.
|
}
|
||||||
return dollar_crate.clone();
|
});
|
||||||
}
|
if replacement.text() == "$crate" {
|
||||||
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
|
// The parent may have many children, and looking for the token may yield incorrect results.
|
||||||
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
|
return None;
|
||||||
parent
|
}
|
||||||
.children_with_tokens()
|
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
|
||||||
.filter_map(NodeOrToken::into_token)
|
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
|
||||||
.find(|it| it.kind() == replacement.kind())
|
parent
|
||||||
.unwrap()
|
.children_with_tokens()
|
||||||
})
|
.filter_map(NodeOrToken::into_token)
|
||||||
|
.find(|it| it.kind() == replacement.kind())
|
||||||
|
},
|
||||||
|
|_| (),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ use hir_expand::{
|
|||||||
attrs::collect_attrs,
|
attrs::collect_attrs,
|
||||||
builtin::{BuiltinFnLikeExpander, EagerExpander},
|
builtin::{BuiltinFnLikeExpander, EagerExpander},
|
||||||
db::ExpandDatabase,
|
db::ExpandDatabase,
|
||||||
files::{FileRangeWrapper, InRealFile},
|
files::{FileRangeWrapper, HirFileRange, InRealFile},
|
||||||
inert_attr_macro::find_builtin_attr_idx,
|
inert_attr_macro::find_builtin_attr_idx,
|
||||||
mod_path::{ModPath, PathKind},
|
mod_path::{ModPath, PathKind},
|
||||||
name::AsName,
|
name::AsName,
|
||||||
@ -262,6 +262,17 @@ impl<DB: HirDatabase> Semantics<'_, DB> {
|
|||||||
self.imp.file_to_module_defs(file.into())
|
self.imp.file_to_module_defs(file.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hir_file_to_module_def(&self, file: impl Into<HirFileId>) -> Option<Module> {
|
||||||
|
self.imp.hir_file_to_module_defs(file.into()).next()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hir_file_to_module_defs(
|
||||||
|
&self,
|
||||||
|
file: impl Into<HirFileId>,
|
||||||
|
) -> impl Iterator<Item = Module> {
|
||||||
|
self.imp.hir_file_to_module_defs(file.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_adt_def(&self, a: &ast::Adt) -> Option<Adt> {
|
pub fn to_adt_def(&self, a: &ast::Adt) -> Option<Adt> {
|
||||||
self.imp.to_def(a)
|
self.imp.to_def(a)
|
||||||
}
|
}
|
||||||
@ -357,6 +368,15 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
tree
|
tree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn adjust_edition(&self, file_id: HirFileId) -> HirFileId {
|
||||||
|
if let Some(editioned_file_id) = file_id.file_id() {
|
||||||
|
self.attach_first_edition(editioned_file_id.file_id(self.db))
|
||||||
|
.map_or(file_id, Into::into)
|
||||||
|
} else {
|
||||||
|
file_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_parent_file(&self, file_id: HirFileId) -> Option<InFile<SyntaxNode>> {
|
pub fn find_parent_file(&self, file_id: HirFileId) -> Option<InFile<SyntaxNode>> {
|
||||||
match file_id {
|
match file_id {
|
||||||
HirFileId::FileId(file_id) => {
|
HirFileId::FileId(file_id) => {
|
||||||
@ -653,7 +673,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
string: &ast::String,
|
string: &ast::String,
|
||||||
) -> Option<Vec<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)>> {
|
) -> Option<Vec<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)>> {
|
||||||
let string_start = string.syntax().text_range().start();
|
let string_start = string.syntax().text_range().start();
|
||||||
let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
|
let token = self.wrap_token_infile(string.syntax().clone());
|
||||||
self.descend_into_macros_breakable(token, |token, _| {
|
self.descend_into_macros_breakable(token, |token, _| {
|
||||||
(|| {
|
(|| {
|
||||||
let token = token.value;
|
let token = token.value;
|
||||||
@ -693,50 +713,95 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the formatting part of the format_args! template string at the given offset.
|
/// Retrieves the formatting part of the format_args! template string at the given offset.
|
||||||
|
///
|
||||||
|
// FIXME: Type the return type
|
||||||
|
/// Returns the range (pre-expansion) in the string literal corresponding to the resolution,
|
||||||
|
/// absolute file range (post-expansion)
|
||||||
|
/// of the part in the format string, the corresponding string token and the resolution if it
|
||||||
|
/// exists.
|
||||||
|
// FIXME: Remove this in favor of `check_for_format_args_template_with_file`
|
||||||
pub fn check_for_format_args_template(
|
pub fn check_for_format_args_template(
|
||||||
&self,
|
&self,
|
||||||
original_token: SyntaxToken,
|
original_token: SyntaxToken,
|
||||||
offset: TextSize,
|
offset: TextSize,
|
||||||
) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
|
) -> Option<(
|
||||||
let string_start = original_token.text_range().start();
|
TextRange,
|
||||||
let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
|
HirFileRange,
|
||||||
self.descend_into_macros_breakable(original_token, |token, _| {
|
ast::String,
|
||||||
(|| {
|
Option<Either<PathResolution, InlineAsmOperand>>,
|
||||||
let token = token.value;
|
)> {
|
||||||
self.resolve_offset_in_format_args(
|
let original_token =
|
||||||
ast::String::cast(token)?,
|
self.wrap_token_infile(original_token).map(ast::String::cast).transpose()?;
|
||||||
offset.checked_sub(string_start)?,
|
self.check_for_format_args_template_with_file(original_token, offset)
|
||||||
)
|
}
|
||||||
.map(|(range, res)| (range + string_start, res))
|
|
||||||
})()
|
/// Retrieves the formatting part of the format_args! template string at the given offset.
|
||||||
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
|
///
|
||||||
})
|
// FIXME: Type the return type
|
||||||
|
/// Returns the range (pre-expansion) in the string literal corresponding to the resolution,
|
||||||
|
/// absolute file range (post-expansion)
|
||||||
|
/// of the part in the format string, the corresponding string token and the resolution if it
|
||||||
|
/// exists.
|
||||||
|
pub fn check_for_format_args_template_with_file(
|
||||||
|
&self,
|
||||||
|
original_token: InFile<ast::String>,
|
||||||
|
offset: TextSize,
|
||||||
|
) -> Option<(
|
||||||
|
TextRange,
|
||||||
|
HirFileRange,
|
||||||
|
ast::String,
|
||||||
|
Option<Either<PathResolution, InlineAsmOperand>>,
|
||||||
|
)> {
|
||||||
|
let relative_offset =
|
||||||
|
offset.checked_sub(original_token.value.syntax().text_range().start())?;
|
||||||
|
self.descend_into_macros_breakable(
|
||||||
|
original_token.as_ref().map(|it| it.syntax().clone()),
|
||||||
|
|token, _| {
|
||||||
|
(|| {
|
||||||
|
let token = token.map(ast::String::cast).transpose()?;
|
||||||
|
self.resolve_offset_in_format_args(token.as_ref(), relative_offset).map(
|
||||||
|
|(range, res)| {
|
||||||
|
(
|
||||||
|
range + original_token.value.syntax().text_range().start(),
|
||||||
|
HirFileRange {
|
||||||
|
file_id: token.file_id,
|
||||||
|
range: range + token.value.syntax().text_range().start(),
|
||||||
|
},
|
||||||
|
token.value,
|
||||||
|
res,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})()
|
||||||
|
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_offset_in_format_args(
|
fn resolve_offset_in_format_args(
|
||||||
&self,
|
&self,
|
||||||
string: ast::String,
|
InFile { value: string, file_id }: InFile<&ast::String>,
|
||||||
offset: TextSize,
|
offset: TextSize,
|
||||||
) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
|
) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
|
||||||
debug_assert!(offset <= string.syntax().text_range().len());
|
debug_assert!(offset <= string.syntax().text_range().len());
|
||||||
let literal = string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
|
let literal = string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
|
||||||
let parent = literal.parent()?;
|
let parent = literal.parent()?;
|
||||||
if let Some(format_args) = ast::FormatArgsExpr::cast(parent.clone()) {
|
if let Some(format_args) = ast::FormatArgsExpr::cast(parent.clone()) {
|
||||||
let source_analyzer = &self.analyze_no_infer(format_args.syntax())?;
|
let source_analyzer =
|
||||||
let format_args = self.wrap_node_infile(format_args);
|
&self.analyze_impl(InFile::new(file_id, format_args.syntax()), None, false)?;
|
||||||
source_analyzer
|
source_analyzer
|
||||||
.resolve_offset_in_format_args(self.db, format_args.as_ref(), offset)
|
.resolve_offset_in_format_args(self.db, InFile::new(file_id, &format_args), offset)
|
||||||
.map(|(range, res)| (range, res.map(Either::Left)))
|
.map(|(range, res)| (range, res.map(Either::Left)))
|
||||||
} else {
|
} else {
|
||||||
let asm = ast::AsmExpr::cast(parent)?;
|
let asm = ast::AsmExpr::cast(parent)?;
|
||||||
let source_analyzer = &self.analyze_no_infer(asm.syntax())?;
|
let source_analyzer =
|
||||||
|
self.analyze_impl(InFile::new(file_id, asm.syntax()), None, false)?;
|
||||||
let line = asm.template().position(|it| *it.syntax() == literal)?;
|
let line = asm.template().position(|it| *it.syntax() == literal)?;
|
||||||
let asm = self.wrap_node_infile(asm);
|
source_analyzer
|
||||||
source_analyzer.resolve_offset_in_asm_template(asm.as_ref(), line, offset).map(
|
.resolve_offset_in_asm_template(InFile::new(file_id, &asm), line, offset)
|
||||||
|(owner, (expr, range, index))| {
|
.map(|(owner, (expr, range, index))| {
|
||||||
(range, Some(Either::Right(InlineAsmOperand { owner, expr, index })))
|
(range, Some(Either::Right(InlineAsmOperand { owner, expr, index })))
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -809,14 +874,11 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
None => return res,
|
None => return res,
|
||||||
};
|
};
|
||||||
let file = self.find_file(node.syntax());
|
let file = self.find_file(node.syntax());
|
||||||
let Some(file_id) = file.file_id.file_id() else {
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
if first == last {
|
if first == last {
|
||||||
// node is just the token, so descend the token
|
// node is just the token, so descend the token
|
||||||
self.descend_into_macros_impl(
|
self.descend_into_macros_impl(
|
||||||
InRealFile::new(file_id, first),
|
InFile::new(file.file_id, first),
|
||||||
&mut |InFile { value, .. }, _ctx| {
|
&mut |InFile { value, .. }, _ctx| {
|
||||||
if let Some(node) = value
|
if let Some(node) = value
|
||||||
.parent_ancestors()
|
.parent_ancestors()
|
||||||
@ -831,14 +893,14 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
} else {
|
} else {
|
||||||
// Descend first and last token, then zip them to look for the node they belong to
|
// Descend first and last token, then zip them to look for the node they belong to
|
||||||
let mut scratch: SmallVec<[_; 1]> = smallvec![];
|
let mut scratch: SmallVec<[_; 1]> = smallvec![];
|
||||||
self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token, _ctx| {
|
self.descend_into_macros_impl(InFile::new(file.file_id, first), &mut |token, _ctx| {
|
||||||
scratch.push(token);
|
scratch.push(token);
|
||||||
CONTINUE_NO_BREAKS
|
CONTINUE_NO_BREAKS
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut scratch = scratch.into_iter();
|
let mut scratch = scratch.into_iter();
|
||||||
self.descend_into_macros_impl(
|
self.descend_into_macros_impl(
|
||||||
InRealFile::new(file_id, last),
|
InFile::new(file.file_id, last),
|
||||||
&mut |InFile { value: last, file_id: last_fid }, _ctx| {
|
&mut |InFile { value: last, file_id: last_fid }, _ctx| {
|
||||||
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
|
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
|
||||||
if first_fid == last_fid {
|
if first_fid == last_fid {
|
||||||
@ -900,22 +962,18 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext),
|
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext),
|
||||||
) {
|
) {
|
||||||
if let Ok(token) = self.wrap_token_infile(token).into_real_file() {
|
self.descend_into_macros_impl(self.wrap_token_infile(token), &mut |t, ctx| {
|
||||||
self.descend_into_macros_impl(token, &mut |t, ctx| {
|
cb(t, ctx);
|
||||||
cb(t, ctx);
|
CONTINUE_NO_BREAKS
|
||||||
CONTINUE_NO_BREAKS
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
|
pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
|
||||||
let mut res = smallvec![];
|
let mut res = smallvec![];
|
||||||
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
|
self.descend_into_macros_impl(self.wrap_token_infile(token.clone()), &mut |t, _ctx| {
|
||||||
self.descend_into_macros_impl(token, &mut |t, _ctx| {
|
res.push(t.value);
|
||||||
res.push(t.value);
|
CONTINUE_NO_BREAKS
|
||||||
CONTINUE_NO_BREAKS
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
if res.is_empty() {
|
if res.is_empty() {
|
||||||
res.push(token);
|
res.push(token);
|
||||||
}
|
}
|
||||||
@ -928,15 +986,13 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
) -> SmallVec<[InFile<SyntaxToken>; 1]> {
|
) -> SmallVec<[InFile<SyntaxToken>; 1]> {
|
||||||
let mut res = smallvec![];
|
let mut res = smallvec![];
|
||||||
let token = self.wrap_token_infile(token);
|
let token = self.wrap_token_infile(token);
|
||||||
if let Ok(token) = token.clone().into_real_file() {
|
self.descend_into_macros_impl(token.clone(), &mut |t, ctx| {
|
||||||
self.descend_into_macros_impl(token, &mut |t, ctx| {
|
if !ctx.is_opaque(self.db) {
|
||||||
if !ctx.is_opaque(self.db) {
|
// Don't descend into opaque contexts
|
||||||
// Don't descend into opaque contexts
|
res.push(t);
|
||||||
res.push(t);
|
}
|
||||||
}
|
CONTINUE_NO_BREAKS
|
||||||
CONTINUE_NO_BREAKS
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
if res.is_empty() {
|
if res.is_empty() {
|
||||||
res.push(token);
|
res.push(token);
|
||||||
}
|
}
|
||||||
@ -945,7 +1001,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
|
|
||||||
pub fn descend_into_macros_breakable<T>(
|
pub fn descend_into_macros_breakable<T>(
|
||||||
&self,
|
&self,
|
||||||
token: InRealFile<SyntaxToken>,
|
token: InFile<SyntaxToken>,
|
||||||
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
|
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
|
||||||
) -> Option<T> {
|
) -> Option<T> {
|
||||||
self.descend_into_macros_impl(token, &mut cb)
|
self.descend_into_macros_impl(token, &mut cb)
|
||||||
@ -974,33 +1030,58 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Descends the token into expansions, returning the tokens that matches the input
|
||||||
|
/// token's [`SyntaxKind`] and text.
|
||||||
|
pub fn descend_into_macros_exact_with_file(
|
||||||
|
&self,
|
||||||
|
token: SyntaxToken,
|
||||||
|
) -> SmallVec<[InFile<SyntaxToken>; 1]> {
|
||||||
|
let mut r = smallvec![];
|
||||||
|
let text = token.text();
|
||||||
|
let kind = token.kind();
|
||||||
|
|
||||||
|
self.descend_into_macros_cb(token.clone(), |InFile { value, file_id }, ctx| {
|
||||||
|
let mapped_kind = value.kind();
|
||||||
|
let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
|
||||||
|
let matches = (kind == mapped_kind || any_ident_match())
|
||||||
|
&& text == value.text()
|
||||||
|
&& !ctx.is_opaque(self.db);
|
||||||
|
if matches {
|
||||||
|
r.push(InFile { value, file_id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if r.is_empty() {
|
||||||
|
r.push(self.wrap_token_infile(token));
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
/// Descends the token into expansions, returning the first token that matches the input
|
/// Descends the token into expansions, returning the first token that matches the input
|
||||||
/// token's [`SyntaxKind`] and text.
|
/// token's [`SyntaxKind`] and text.
|
||||||
pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
|
pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
|
||||||
let text = token.text();
|
let text = token.text();
|
||||||
let kind = token.kind();
|
let kind = token.kind();
|
||||||
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
|
self.descend_into_macros_breakable(
|
||||||
self.descend_into_macros_breakable(token, |InFile { value, file_id: _ }, _ctx| {
|
self.wrap_token_infile(token.clone()),
|
||||||
|
|InFile { value, file_id: _ }, _ctx| {
|
||||||
let mapped_kind = value.kind();
|
let mapped_kind = value.kind();
|
||||||
let any_ident_match =
|
let any_ident_match =
|
||||||
|| kind.is_any_identifier() && value.kind().is_any_identifier();
|
|| kind.is_any_identifier() && value.kind().is_any_identifier();
|
||||||
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
|
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
|
||||||
if matches { ControlFlow::Break(value) } else { ControlFlow::Continue(()) }
|
if matches { ControlFlow::Break(value) } else { ControlFlow::Continue(()) }
|
||||||
})
|
},
|
||||||
} else {
|
)
|
||||||
None
|
|
||||||
}
|
|
||||||
.unwrap_or(token)
|
.unwrap_or(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn descend_into_macros_impl<T>(
|
fn descend_into_macros_impl<T>(
|
||||||
&self,
|
&self,
|
||||||
InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
|
InFile { value: token, file_id }: InFile<SyntaxToken>,
|
||||||
f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
|
f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
|
||||||
) -> Option<T> {
|
) -> Option<T> {
|
||||||
let _p = tracing::info_span!("descend_into_macros_impl").entered();
|
let _p = tracing::info_span!("descend_into_macros_impl").entered();
|
||||||
|
|
||||||
let span = self.db.real_span_map(file_id).span_for_range(token.text_range());
|
let span = self.db.span_map(file_id).span_for_range(token.text_range());
|
||||||
|
|
||||||
// Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
|
// Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
|
||||||
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
|
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
|
||||||
@ -1024,17 +1105,16 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
// the tokens themselves aren't that interesting as the span that is being used to map
|
// the tokens themselves aren't that interesting as the span that is being used to map
|
||||||
// things down never changes.
|
// things down never changes.
|
||||||
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![];
|
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![];
|
||||||
let include = self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id);
|
let include = file_id.file_id().and_then(|file_id| {
|
||||||
|
self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id)
|
||||||
|
});
|
||||||
match include {
|
match include {
|
||||||
Some(include) => {
|
Some(include) => {
|
||||||
// include! inputs are always from real files, so they only need to be handled once upfront
|
// include! inputs are always from real files, so they only need to be handled once upfront
|
||||||
process_expansion_for_token(&mut stack, include)?;
|
process_expansion_for_token(&mut stack, include)?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
stack.push((
|
stack.push((file_id, smallvec![(token, span.ctx)]));
|
||||||
file_id.into(),
|
|
||||||
smallvec![(token, SyntaxContext::root(file_id.edition(self.db)))],
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1678,6 +1758,11 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
self.with_ctx(|ctx| ctx.file_to_def(file).to_owned()).into_iter().map(Module::from)
|
self.with_ctx(|ctx| ctx.file_to_def(file).to_owned()).into_iter().map(Module::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hir_file_to_module_defs(&self, file: HirFileId) -> impl Iterator<Item = Module> {
|
||||||
|
// FIXME: Do we need to care about inline modules for macro expansions?
|
||||||
|
self.file_to_module_defs(file.original_file_respecting_includes(self.db).file_id(self.db))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> {
|
pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> {
|
||||||
self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope {
|
self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope {
|
||||||
db: self.db,
|
db: self.db,
|
||||||
|
@ -1303,6 +1303,7 @@ impl<'db> SourceAnalyzer<'db> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the range of the implicit template argument and its resolution at the given `offset`
|
||||||
pub(crate) fn resolve_offset_in_format_args(
|
pub(crate) fn resolve_offset_in_format_args(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn HirDatabase,
|
db: &'db dyn HirDatabase,
|
||||||
|
@ -128,11 +128,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
|
|||||||
format!("Import `{import_name}`"),
|
format!("Import `{import_name}`"),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let scope = match scope.clone() {
|
let scope = builder.make_import_scope_mut(scope.clone());
|
||||||
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
|
||||||
};
|
|
||||||
insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use);
|
insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -153,11 +149,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
|
|||||||
format!("Import `{import_name} as _`"),
|
format!("Import `{import_name} as _`"),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let scope = match scope.clone() {
|
let scope = builder.make_import_scope_mut(scope.clone());
|
||||||
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
|
||||||
};
|
|
||||||
insert_use_as_alias(
|
insert_use_as_alias(
|
||||||
&scope,
|
&scope,
|
||||||
mod_path_to_ast(&import_path, edition),
|
mod_path_to_ast(&import_path, edition),
|
||||||
@ -1877,4 +1869,30 @@ fn main() {
|
|||||||
",
|
",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn carries_cfg_attr() {
|
||||||
|
check_assist(
|
||||||
|
auto_import,
|
||||||
|
r#"
|
||||||
|
mod m {
|
||||||
|
pub struct S;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn foo(_: S$0) {}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
use m::S;
|
||||||
|
|
||||||
|
mod m {
|
||||||
|
pub struct S;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn foo(_: S) {}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,12 +312,8 @@ fn replace_usages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add imports across modules where needed
|
// add imports across modules where needed
|
||||||
if let Some((import_scope, path)) = import_data {
|
if let Some((scope, path)) = import_data {
|
||||||
let scope = match import_scope {
|
let scope = edit.make_import_scope_mut(scope);
|
||||||
ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
|
|
||||||
};
|
|
||||||
delayed_mutations.push((scope, path));
|
delayed_mutations.push((scope, path));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -996,7 +996,8 @@ pub struct $0Foo {
|
|||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
r#"
|
r#"
|
||||||
pub struct Foo(#[my_custom_attr] u32);
|
pub struct Foo(#[my_custom_attr]
|
||||||
|
u32);
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -923,7 +923,8 @@ where
|
|||||||
pub struct $0Foo(#[my_custom_attr] u32);
|
pub struct $0Foo(#[my_custom_attr] u32);
|
||||||
"#,
|
"#,
|
||||||
r#"
|
r#"
|
||||||
pub struct Foo { #[my_custom_attr] field1: u32 }
|
pub struct Foo { #[my_custom_attr]
|
||||||
|
field1: u32 }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -204,12 +204,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
|||||||
.kind
|
.kind
|
||||||
.is_some_and(|kind| matches!(kind, FlowKind::Break(_, _) | FlowKind::Continue(_)))
|
.is_some_and(|kind| matches!(kind, FlowKind::Break(_, _) | FlowKind::Continue(_)))
|
||||||
{
|
{
|
||||||
let scope = match scope {
|
let scope = builder.make_import_scope_mut(scope);
|
||||||
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let control_flow_enum =
|
let control_flow_enum =
|
||||||
FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow();
|
FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow();
|
||||||
|
|
||||||
|
@ -81,11 +81,7 @@ pub(crate) fn replace_qualified_name_with_use(
|
|||||||
|builder| {
|
|builder| {
|
||||||
// Now that we've brought the name into scope, re-qualify all paths that could be
|
// Now that we've brought the name into scope, re-qualify all paths that could be
|
||||||
// affected (that is, all paths inside the node we added the `use` to).
|
// affected (that is, all paths inside the node we added the `use` to).
|
||||||
let scope = match scope {
|
let scope = builder.make_import_scope_mut(scope);
|
||||||
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
|
||||||
};
|
|
||||||
shorten_paths(scope.as_syntax_node(), &original_path);
|
shorten_paths(scope.as_syntax_node(), &original_path);
|
||||||
let path = drop_generic_args(&original_path);
|
let path = drop_generic_args(&original_path);
|
||||||
let edition = ctx
|
let edition = ctx
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use ide_db::imports::insert_use::ImportScope;
|
|
||||||
use syntax::{
|
use syntax::{
|
||||||
TextRange,
|
TextRange,
|
||||||
ast::{self, AstNode, HasArgList, prec::ExprPrecedence},
|
ast::{self, AstNode, HasArgList, prec::ExprPrecedence},
|
||||||
@ -114,11 +113,7 @@ fn add_import(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if let Some(scope) = scope {
|
if let Some(scope) = scope {
|
||||||
let scope = match scope {
|
let scope = edit.make_import_scope_mut(scope);
|
||||||
ImportScope::File(it) => ImportScope::File(edit.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)),
|
|
||||||
};
|
|
||||||
ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
|
ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,107 +60,87 @@ pub struct InsertUseConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ImportScope {
|
pub struct ImportScope {
|
||||||
|
pub kind: ImportScopeKind,
|
||||||
|
pub required_cfgs: Vec<ast::Attr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ImportScopeKind {
|
||||||
File(ast::SourceFile),
|
File(ast::SourceFile),
|
||||||
Module(ast::ItemList),
|
Module(ast::ItemList),
|
||||||
Block(ast::StmtList),
|
Block(ast::StmtList),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportScope {
|
impl ImportScope {
|
||||||
// FIXME: Remove this?
|
|
||||||
#[cfg(test)]
|
|
||||||
fn from(syntax: SyntaxNode) -> Option<Self> {
|
|
||||||
use syntax::match_ast;
|
|
||||||
fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
|
|
||||||
attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
|
|
||||||
}
|
|
||||||
match_ast! {
|
|
||||||
match syntax {
|
|
||||||
ast::Module(module) => module.item_list().map(ImportScope::Module),
|
|
||||||
ast::SourceFile(file) => Some(ImportScope::File(file)),
|
|
||||||
ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().and_then(|it| it.stmt_list().map(ImportScope::Block))).flatten(),
|
|
||||||
ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? {
|
|
||||||
ast::Expr::BlockExpr(block) => Some(block),
|
|
||||||
_ => None,
|
|
||||||
}).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
|
|
||||||
ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? {
|
|
||||||
ast::Expr::BlockExpr(block) => Some(block),
|
|
||||||
_ => None,
|
|
||||||
}).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
|
|
||||||
_ => None,
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
|
/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
|
||||||
/// Returns the original source node inside attributes.
|
/// Returns the original source node inside attributes.
|
||||||
pub fn find_insert_use_container(
|
pub fn find_insert_use_container(
|
||||||
position: &SyntaxNode,
|
position: &SyntaxNode,
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
|
// The closest block expression ancestor
|
||||||
attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
|
let mut block = None;
|
||||||
}
|
let mut required_cfgs = Vec::new();
|
||||||
|
|
||||||
// Walk up the ancestor tree searching for a suitable node to do insertions on
|
// Walk up the ancestor tree searching for a suitable node to do insertions on
|
||||||
// with special handling on cfg-gated items, in which case we want to insert imports locally
|
// with special handling on cfg-gated items, in which case we want to insert imports locally
|
||||||
// or FIXME: annotate inserted imports with the same cfg
|
// or FIXME: annotate inserted imports with the same cfg
|
||||||
for syntax in sema.ancestors_with_macros(position.clone()) {
|
for syntax in sema.ancestors_with_macros(position.clone()) {
|
||||||
if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
|
if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
|
||||||
return Some(ImportScope::File(file));
|
return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
|
||||||
} else if let Some(item) = ast::Item::cast(syntax) {
|
} else if let Some(module) = ast::Module::cast(syntax.clone()) {
|
||||||
return match item {
|
// early return is important here, if we can't find the original module
|
||||||
ast::Item::Const(konst) if contains_cfg_attr(&konst) => {
|
// in the input there is no way for us to insert an import anywhere.
|
||||||
// FIXME: Instead of bailing out with None, we should note down that
|
return sema
|
||||||
// this import needs an attribute added
|
.original_ast_node(module)?
|
||||||
match sema.original_ast_node(konst)?.body()? {
|
.item_list()
|
||||||
ast::Expr::BlockExpr(block) => block,
|
.map(ImportScopeKind::Module)
|
||||||
_ => return None,
|
.map(|kind| ImportScope { kind, required_cfgs });
|
||||||
|
} else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
|
||||||
|
if block.is_none() {
|
||||||
|
if let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone()) {
|
||||||
|
if let Some(b) = sema.original_ast_node(b) {
|
||||||
|
block = b.stmt_list();
|
||||||
}
|
}
|
||||||
.stmt_list()
|
|
||||||
.map(ImportScope::Block)
|
|
||||||
}
|
}
|
||||||
ast::Item::Fn(func) if contains_cfg_attr(&func) => {
|
}
|
||||||
// FIXME: Instead of bailing out with None, we should note down that
|
if has_attrs
|
||||||
// this import needs an attribute added
|
.attrs()
|
||||||
sema.original_ast_node(func)?.body()?.stmt_list().map(ImportScope::Block)
|
.any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
|
||||||
|
{
|
||||||
|
if let Some(b) = block {
|
||||||
|
return Some(ImportScope {
|
||||||
|
kind: ImportScopeKind::Block(b),
|
||||||
|
required_cfgs,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ast::Item::Static(statik) if contains_cfg_attr(&statik) => {
|
required_cfgs.extend(has_attrs.attrs().filter(|attr| {
|
||||||
// FIXME: Instead of bailing out with None, we should note down that
|
attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
|
||||||
// this import needs an attribute added
|
}));
|
||||||
match sema.original_ast_node(statik)?.body()? {
|
}
|
||||||
ast::Expr::BlockExpr(block) => block,
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
.stmt_list()
|
|
||||||
.map(ImportScope::Block)
|
|
||||||
}
|
|
||||||
ast::Item::Module(module) => {
|
|
||||||
// early return is important here, if we can't find the original module
|
|
||||||
// in the input there is no way for us to insert an import anywhere.
|
|
||||||
sema.original_ast_node(module)?.item_list().map(ImportScope::Module)
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_syntax_node(&self) -> &SyntaxNode {
|
pub fn as_syntax_node(&self) -> &SyntaxNode {
|
||||||
match self {
|
match &self.kind {
|
||||||
ImportScope::File(file) => file.syntax(),
|
ImportScopeKind::File(file) => file.syntax(),
|
||||||
ImportScope::Module(item_list) => item_list.syntax(),
|
ImportScopeKind::Module(item_list) => item_list.syntax(),
|
||||||
ImportScope::Block(block) => block.syntax(),
|
ImportScopeKind::Block(block) => block.syntax(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_for_update(&self) -> Self {
|
pub fn clone_for_update(&self) -> Self {
|
||||||
match self {
|
Self {
|
||||||
ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
|
kind: match &self.kind {
|
||||||
ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
|
ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
|
||||||
ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
|
ImportScopeKind::Module(item_list) => {
|
||||||
|
ImportScopeKind::Module(item_list.clone_for_update())
|
||||||
|
}
|
||||||
|
ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
|
||||||
|
},
|
||||||
|
required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +196,11 @@ fn insert_use_with_alias_option(
|
|||||||
use_tree.wrap_in_tree_list();
|
use_tree.wrap_in_tree_list();
|
||||||
}
|
}
|
||||||
let use_item = make::use_(None, use_tree).clone_for_update();
|
let use_item = make::use_(None, use_tree).clone_for_update();
|
||||||
|
for attr in
|
||||||
|
scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
|
||||||
|
{
|
||||||
|
ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
|
||||||
|
}
|
||||||
|
|
||||||
// merge into existing imports if possible
|
// merge into existing imports if possible
|
||||||
if let Some(mb) = mb {
|
if let Some(mb) = mb {
|
||||||
@ -229,7 +214,6 @@ fn insert_use_with_alias_option(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// either we weren't allowed to merge or there is no import that fits the merge conditions
|
// either we weren't allowed to merge or there is no import that fits the merge conditions
|
||||||
// so look for the place we have to insert to
|
// so look for the place we have to insert to
|
||||||
insert_use_(scope, use_item, cfg.group);
|
insert_use_(scope, use_item, cfg.group);
|
||||||
@ -316,10 +300,10 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
|
|||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let mut use_stmts = match scope {
|
let mut use_stmts = match &scope.kind {
|
||||||
ImportScope::File(f) => f.items(),
|
ImportScopeKind::File(f) => f.items(),
|
||||||
ImportScope::Module(m) => m.items(),
|
ImportScopeKind::Module(m) => m.items(),
|
||||||
ImportScope::Block(b) => b.items(),
|
ImportScopeKind::Block(b) => b.items(),
|
||||||
}
|
}
|
||||||
.filter_map(use_stmt);
|
.filter_map(use_stmt);
|
||||||
let mut res = ImportGranularityGuess::Unknown;
|
let mut res = ImportGranularityGuess::Unknown;
|
||||||
@ -463,12 +447,12 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let l_curly = match scope {
|
let l_curly = match &scope.kind {
|
||||||
ImportScope::File(_) => None,
|
ImportScopeKind::File(_) => None,
|
||||||
// don't insert the imports before the item list/block expr's opening curly brace
|
// don't insert the imports before the item list/block expr's opening curly brace
|
||||||
ImportScope::Module(item_list) => item_list.l_curly_token(),
|
ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
|
||||||
// don't insert the imports before the item list's opening curly brace
|
// don't insert the imports before the item list's opening curly brace
|
||||||
ImportScope::Block(block) => block.l_curly_token(),
|
ImportScopeKind::Block(block) => block.l_curly_token(),
|
||||||
};
|
};
|
||||||
// there are no imports in this file at all
|
// there are no imports in this file at all
|
||||||
// so put the import after all inner module attributes and possible license header comments
|
// so put the import after all inner module attributes and possible license header comments
|
||||||
|
@ -23,7 +23,7 @@ struct Struct;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn respects_cfg_attr_fn() {
|
fn respects_cfg_attr_fn_body() {
|
||||||
check(
|
check(
|
||||||
r"bar::Bar",
|
r"bar::Bar",
|
||||||
r#"
|
r#"
|
||||||
@ -40,6 +40,25 @@ fn foo() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn respects_cfg_attr_fn_sig() {
|
||||||
|
check(
|
||||||
|
r"bar::Bar",
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
fn foo($0) {}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
use bar::Bar;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn foo() {}
|
||||||
|
"#,
|
||||||
|
ImportGranularity::Crate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn respects_cfg_attr_const() {
|
fn respects_cfg_attr_const() {
|
||||||
check(
|
check(
|
||||||
@ -58,6 +77,51 @@ const FOO: Bar = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn respects_cfg_attr_impl() {
|
||||||
|
check(
|
||||||
|
r"bar::Bar",
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
impl () {$0}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
use bar::Bar;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl () {}
|
||||||
|
"#,
|
||||||
|
ImportGranularity::Crate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn respects_cfg_attr_multiple_layers() {
|
||||||
|
check(
|
||||||
|
r"bar::Bar",
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
impl () {
|
||||||
|
#[cfg(test2)]
|
||||||
|
fn f($0) {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(test2)]
|
||||||
|
use bar::Bar;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl () {
|
||||||
|
#[cfg(test2)]
|
||||||
|
fn f() {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
ImportGranularity::Crate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_skips_lone_glob_imports() {
|
fn insert_skips_lone_glob_imports() {
|
||||||
check(
|
check(
|
||||||
@ -813,7 +877,7 @@ use {std::io};",
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_groups_skip_attributed() {
|
fn merge_groups_cfg_vs_no_cfg() {
|
||||||
check_crate(
|
check_crate(
|
||||||
"std::io",
|
"std::io",
|
||||||
r#"
|
r#"
|
||||||
@ -836,6 +900,25 @@ use {std::io};
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_groups_cfg_matching() {
|
||||||
|
check_crate(
|
||||||
|
"std::io",
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "gated")] use std::fmt::{Result, Display};
|
||||||
|
|
||||||
|
#[cfg(feature = "gated")]
|
||||||
|
fn f($0) {}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "gated")] use std::{fmt::{Display, Result}, io};
|
||||||
|
|
||||||
|
#[cfg(feature = "gated")]
|
||||||
|
fn f() {}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn split_out_merge() {
|
fn split_out_merge() {
|
||||||
// FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}`
|
// FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}`
|
||||||
@ -1259,12 +1342,14 @@ fn check_with_config(
|
|||||||
};
|
};
|
||||||
let sema = &Semantics::new(&db);
|
let sema = &Semantics::new(&db);
|
||||||
let source_file = sema.parse(file_id);
|
let source_file = sema.parse(file_id);
|
||||||
let syntax = source_file.syntax().clone_for_update();
|
|
||||||
let file = pos
|
let file = pos
|
||||||
.and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent())
|
.and_then(|pos| source_file.syntax().token_at_offset(pos.expect_offset()).next()?.parent())
|
||||||
.and_then(|it| ImportScope::find_insert_use_container(&it, sema))
|
.and_then(|it| ImportScope::find_insert_use_container(&it, sema))
|
||||||
.or_else(|| ImportScope::from(syntax))
|
.unwrap_or_else(|| ImportScope {
|
||||||
.unwrap();
|
kind: ImportScopeKind::File(source_file),
|
||||||
|
required_cfgs: vec![],
|
||||||
|
})
|
||||||
|
.clone_for_update();
|
||||||
let path = ast::SourceFile::parse(&format!("use {path};"), span::Edition::CURRENT)
|
let path = ast::SourceFile::parse(&format!("use {path};"), span::Edition::CURRENT)
|
||||||
.tree()
|
.tree()
|
||||||
.syntax()
|
.syntax()
|
||||||
@ -1349,7 +1434,7 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: ImportGranularityGuess) {
|
fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: ImportGranularityGuess) {
|
||||||
let syntax = ast::SourceFile::parse(ra_fixture, span::Edition::CURRENT).tree().syntax().clone();
|
let syntax = ast::SourceFile::parse(ra_fixture, span::Edition::CURRENT).tree();
|
||||||
let file = ImportScope::from(syntax).unwrap();
|
let file = ImportScope { kind: ImportScopeKind::File(syntax), required_cfgs: vec![] };
|
||||||
assert_eq!(super::guess_granularity_from_scope(&file), expected);
|
assert_eq!(super::guess_granularity_from_scope(&file), expected);
|
||||||
}
|
}
|
||||||
|
@ -961,12 +961,16 @@ impl<'a> FindUsages<'a> {
|
|||||||
// Search for occurrences of the items name
|
// Search for occurrences of the items name
|
||||||
for offset in Self::match_indices(&text, finder, search_range) {
|
for offset in Self::match_indices(&text, finder, search_range) {
|
||||||
let ret = tree.token_at_offset(offset).any(|token| {
|
let ret = tree.token_at_offset(offset).any(|token| {
|
||||||
let Some(str_token) = ast::String::cast(token.clone()) else { return false };
|
if let Some((range, _frange, string_token, Some(nameres))) =
|
||||||
if let Some((range, Some(nameres))) =
|
sema.check_for_format_args_template(token.clone(), offset)
|
||||||
sema.check_for_format_args_template(token, offset)
|
|
||||||
{
|
{
|
||||||
return self
|
return self.found_format_args_ref(
|
||||||
.found_format_args_ref(file_id, range, str_token, nameres, sink);
|
file_id,
|
||||||
|
range,
|
||||||
|
string_token,
|
||||||
|
nameres,
|
||||||
|
sink,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
use std::{collections::hash_map::Entry, fmt, iter, mem};
|
use std::{collections::hash_map::Entry, fmt, iter, mem};
|
||||||
|
|
||||||
|
use crate::imports::insert_use::{ImportScope, ImportScopeKind};
|
||||||
use crate::text_edit::{TextEdit, TextEditBuilder};
|
use crate::text_edit::{TextEdit, TextEditBuilder};
|
||||||
use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff};
|
use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff};
|
||||||
use base_db::AnchoredPathBuf;
|
use base_db::AnchoredPathBuf;
|
||||||
@ -367,6 +368,17 @@ impl SourceChangeBuilder {
|
|||||||
pub fn make_mut<N: AstNode>(&mut self, node: N) -> N {
|
pub fn make_mut<N: AstNode>(&mut self, node: N) -> N {
|
||||||
self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
|
self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_import_scope_mut(&mut self, scope: ImportScope) -> ImportScope {
|
||||||
|
ImportScope {
|
||||||
|
kind: match scope.kind.clone() {
|
||||||
|
ImportScopeKind::File(it) => ImportScopeKind::File(self.make_mut(it)),
|
||||||
|
ImportScopeKind::Module(it) => ImportScopeKind::Module(self.make_mut(it)),
|
||||||
|
ImportScopeKind::Block(it) => ImportScopeKind::Block(self.make_mut(it)),
|
||||||
|
},
|
||||||
|
required_cfgs: scope.required_cfgs.iter().map(|it| self.make_mut(it.clone())).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Returns a copy of the `node`, suitable for mutation.
|
/// Returns a copy of the `node`, suitable for mutation.
|
||||||
///
|
///
|
||||||
/// Syntax trees in rust-analyzer are typically immutable, and mutating
|
/// Syntax trees in rust-analyzer are typically immutable, and mutating
|
||||||
|
@ -137,11 +137,7 @@ pub(crate) fn json_in_items(
|
|||||||
)
|
)
|
||||||
.with_fixes(Some(vec![{
|
.with_fixes(Some(vec![{
|
||||||
let mut scb = SourceChangeBuilder::new(vfs_file_id);
|
let mut scb = SourceChangeBuilder::new(vfs_file_id);
|
||||||
let scope = match import_scope {
|
let scope = scb.make_import_scope_mut(import_scope);
|
||||||
ImportScope::File(it) => ImportScope::File(scb.make_mut(it)),
|
|
||||||
ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)),
|
|
||||||
ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)),
|
|
||||||
};
|
|
||||||
let current_module = semantics_scope.module();
|
let current_module = semantics_scope.module();
|
||||||
|
|
||||||
let cfg = ImportPathConfig {
|
let cfg = ImportPathConfig {
|
||||||
|
@ -67,7 +67,7 @@ pub(crate) fn goto_definition(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((range, resolution)) =
|
if let Some((range, _, _, resolution)) =
|
||||||
sema.check_for_format_args_template(original_token.clone(), offset)
|
sema.check_for_format_args_template(original_token.clone(), offset)
|
||||||
{
|
{
|
||||||
return Some(RangeInfo::new(
|
return Some(RangeInfo::new(
|
||||||
|
@ -53,7 +53,9 @@ pub(crate) fn goto_type_definition(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if let Some((range, resolution)) = sema.check_for_format_args_template(token.clone(), offset) {
|
if let Some((range, _, _, resolution)) =
|
||||||
|
sema.check_for_format_args_template(token.clone(), offset)
|
||||||
|
{
|
||||||
if let Some(ty) = resolution.and_then(|res| match Definition::from(res) {
|
if let Some(ty) = resolution.and_then(|res| match Definition::from(res) {
|
||||||
Definition::Const(it) => Some(it.ty(db)),
|
Definition::Const(it) => Some(it.ty(db)),
|
||||||
Definition::Static(it) => Some(it.ty(db)),
|
Definition::Static(it) => Some(it.ty(db)),
|
||||||
|
@ -11,7 +11,6 @@ use ide_db::{
|
|||||||
preorder_expr_with_ctx_checker,
|
preorder_expr_with_ctx_checker,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use span::FileId;
|
|
||||||
use syntax::{
|
use syntax::{
|
||||||
AstNode,
|
AstNode,
|
||||||
SyntaxKind::{self, IDENT, INT_NUMBER},
|
SyntaxKind::{self, IDENT, INT_NUMBER},
|
||||||
@ -61,13 +60,12 @@ pub(crate) fn highlight_related(
|
|||||||
let file_id = sema
|
let file_id = sema
|
||||||
.attach_first_edition(file_id)
|
.attach_first_edition(file_id)
|
||||||
.unwrap_or_else(|| EditionedFileId::current_edition(sema.db, file_id));
|
.unwrap_or_else(|| EditionedFileId::current_edition(sema.db, file_id));
|
||||||
let span_file_id = file_id.editioned_file_id(sema.db);
|
|
||||||
let syntax = sema.parse(file_id).syntax().clone();
|
let syntax = sema.parse(file_id).syntax().clone();
|
||||||
|
|
||||||
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
|
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
|
||||||
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
|
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
|
||||||
T![->] => 4,
|
T![->] => 4,
|
||||||
kind if kind.is_keyword(span_file_id.edition()) => 3,
|
kind if kind.is_keyword(file_id.edition(sema.db)) => 3,
|
||||||
IDENT | INT_NUMBER => 2,
|
IDENT | INT_NUMBER => 2,
|
||||||
T![|] => 1,
|
T![|] => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
@ -92,18 +90,11 @@ pub(crate) fn highlight_related(
|
|||||||
T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {
|
T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => {
|
||||||
highlight_unsafe_points(sema, token).remove(&file_id)
|
highlight_unsafe_points(sema, token).remove(&file_id)
|
||||||
}
|
}
|
||||||
T![|] if config.closure_captures => {
|
T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
|
||||||
highlight_closure_captures(sema, token, file_id, span_file_id.file_id())
|
T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
|
||||||
|
_ if config.references => {
|
||||||
|
highlight_references(sema, token, FilePosition { file_id, offset })
|
||||||
}
|
}
|
||||||
T![move] if config.closure_captures => {
|
|
||||||
highlight_closure_captures(sema, token, file_id, span_file_id.file_id())
|
|
||||||
}
|
|
||||||
_ if config.references => highlight_references(
|
|
||||||
sema,
|
|
||||||
token,
|
|
||||||
FilePosition { file_id, offset },
|
|
||||||
span_file_id.file_id(),
|
|
||||||
),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +103,6 @@ fn highlight_closure_captures(
|
|||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
file_id: EditionedFileId,
|
file_id: EditionedFileId,
|
||||||
vfs_file_id: FileId,
|
|
||||||
) -> Option<Vec<HighlightedRange>> {
|
) -> Option<Vec<HighlightedRange>> {
|
||||||
let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
|
let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
|
||||||
let search_range = closure.body()?.syntax().text_range();
|
let search_range = closure.body()?.syntax().text_range();
|
||||||
@ -145,7 +135,7 @@ fn highlight_closure_captures(
|
|||||||
.sources(sema.db)
|
.sources(sema.db)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|x| x.to_nav(sema.db))
|
.flat_map(|x| x.to_nav(sema.db))
|
||||||
.filter(|decl| decl.file_id == vfs_file_id)
|
.filter(|decl| decl.file_id == file_id.file_id(sema.db))
|
||||||
.filter_map(|decl| decl.focus_range)
|
.filter_map(|decl| decl.focus_range)
|
||||||
.map(move |range| HighlightedRange { range, category })
|
.map(move |range| HighlightedRange { range, category })
|
||||||
.chain(usages)
|
.chain(usages)
|
||||||
@ -158,9 +148,8 @@ fn highlight_references(
|
|||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
FilePosition { file_id, offset }: FilePosition,
|
FilePosition { file_id, offset }: FilePosition,
|
||||||
vfs_file_id: FileId,
|
|
||||||
) -> Option<Vec<HighlightedRange>> {
|
) -> Option<Vec<HighlightedRange>> {
|
||||||
let defs = if let Some((range, resolution)) =
|
let defs = if let Some((range, _, _, resolution)) =
|
||||||
sema.check_for_format_args_template(token.clone(), offset)
|
sema.check_for_format_args_template(token.clone(), offset)
|
||||||
{
|
{
|
||||||
match resolution.map(Definition::from) {
|
match resolution.map(Definition::from) {
|
||||||
@ -270,7 +259,7 @@ fn highlight_references(
|
|||||||
.sources(sema.db)
|
.sources(sema.db)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|x| x.to_nav(sema.db))
|
.flat_map(|x| x.to_nav(sema.db))
|
||||||
.filter(|decl| decl.file_id == vfs_file_id)
|
.filter(|decl| decl.file_id == file_id.file_id(sema.db))
|
||||||
.filter_map(|decl| decl.focus_range)
|
.filter_map(|decl| decl.focus_range)
|
||||||
.map(|range| HighlightedRange { range, category })
|
.map(|range| HighlightedRange { range, category })
|
||||||
.for_each(|x| {
|
.for_each(|x| {
|
||||||
@ -288,7 +277,7 @@ fn highlight_references(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
for nav in navs {
|
for nav in navs {
|
||||||
if nav.file_id != vfs_file_id {
|
if nav.file_id != file_id.file_id(sema.db) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let hl_range = nav.focus_range.map(|range| {
|
let hl_range = nav.focus_range.map(|range| {
|
||||||
|
@ -200,7 +200,7 @@ fn hover_offset(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((range, resolution)) =
|
if let Some((range, _, _, resolution)) =
|
||||||
sema.check_for_format_args_template(original_token.clone(), offset)
|
sema.check_for_format_args_template(original_token.clone(), offset)
|
||||||
{
|
{
|
||||||
let res = hover_for_definition(
|
let res = hover_for_definition(
|
||||||
|
@ -514,7 +514,6 @@ impl Analysis {
|
|||||||
self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
|
self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds all usages of the reference at point.
|
|
||||||
pub fn find_all_refs(
|
pub fn find_all_refs(
|
||||||
&self,
|
&self,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
//! for text occurrences of the identifier. If there's an `ast::NameRef`
|
//! for text occurrences of the identifier. If there's an `ast::NameRef`
|
||||||
//! at the index that the match starts at and its tree parent is
|
//! at the index that the match starts at and its tree parent is
|
||||||
//! resolved to the search element definition, we get a reference.
|
//! resolved to the search element definition, we get a reference.
|
||||||
|
//!
|
||||||
|
//! Special handling for constructors/initializations:
|
||||||
|
//! When searching for references to a struct/enum/variant, if the cursor is positioned on:
|
||||||
|
//! - `{` after a struct/enum/variant definition
|
||||||
|
//! - `(` for tuple structs/variants
|
||||||
|
//! - `;` for unit structs
|
||||||
|
//! - The type name in a struct/enum/variant definition
|
||||||
|
//! Then only constructor/initialization usages will be shown, filtering out other references.
|
||||||
|
|
||||||
use hir::{PathResolution, Semantics};
|
use hir::{PathResolution, Semantics};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
@ -28,27 +36,76 @@ use syntax::{
|
|||||||
|
|
||||||
use crate::{FilePosition, HighlightedRange, NavigationTarget, TryToNav, highlight_related};
|
use crate::{FilePosition, HighlightedRange, NavigationTarget, TryToNav, highlight_related};
|
||||||
|
|
||||||
|
/// Result of a reference search operation.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ReferenceSearchResult {
|
pub struct ReferenceSearchResult {
|
||||||
|
/// Information about the declaration site of the searched item.
|
||||||
|
/// For ADTs (structs/enums), this points to the type definition.
|
||||||
|
/// May be None for primitives or items without clear declaration sites.
|
||||||
pub declaration: Option<Declaration>,
|
pub declaration: Option<Declaration>,
|
||||||
|
/// All references found, grouped by file.
|
||||||
|
/// For ADTs when searching from a constructor position (e.g. on '{', '(', ';'),
|
||||||
|
/// this only includes constructor/initialization usages.
|
||||||
|
/// The map key is the file ID, and the value is a vector of (range, category) pairs.
|
||||||
|
/// - range: The text range of the reference in the file
|
||||||
|
/// - category: Metadata about how the reference is used (read/write/etc)
|
||||||
pub references: IntMap<FileId, Vec<(TextRange, ReferenceCategory)>>,
|
pub references: IntMap<FileId, Vec<(TextRange, ReferenceCategory)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information about the declaration site of a searched item.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Declaration {
|
pub struct Declaration {
|
||||||
|
/// Navigation information to jump to the declaration
|
||||||
pub nav: NavigationTarget,
|
pub nav: NavigationTarget,
|
||||||
|
/// Whether the declared item is mutable (relevant for variables)
|
||||||
pub is_mut: bool,
|
pub is_mut: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feature: Find All References
|
// Feature: Find All References
|
||||||
//
|
//
|
||||||
// Shows all references of the item at the cursor location
|
// Shows all references of the item at the cursor location. This includes:
|
||||||
|
// - Direct references to variables, functions, types, etc.
|
||||||
|
// - Constructor/initialization references when cursor is on struct/enum definition tokens
|
||||||
|
// - References in patterns and type contexts
|
||||||
|
// - References through dereferencing and borrowing
|
||||||
|
// - References in macro expansions
|
||||||
|
//
|
||||||
|
// Special handling for constructors:
|
||||||
|
// - When the cursor is on `{`, `(`, or `;` in a struct/enum definition
|
||||||
|
// - When the cursor is on the type name in a struct/enum definition
|
||||||
|
// These cases will show only constructor/initialization usages of the type
|
||||||
//
|
//
|
||||||
// | Editor | Shortcut |
|
// | Editor | Shortcut |
|
||||||
// |---------|----------|
|
// |---------|----------|
|
||||||
// | VS Code | <kbd>Shift+Alt+F12</kbd> |
|
// | VS Code | <kbd>Shift+Alt+F12</kbd> |
|
||||||
//
|
//
|
||||||
// 
|
// 
|
||||||
|
|
||||||
|
/// Find all references to the item at the given position.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `sema` - Semantic analysis context
|
||||||
|
/// * `position` - Position in the file where to look for the item
|
||||||
|
/// * `search_scope` - Optional scope to limit the search (e.g. current crate only)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns `None` if no valid item is found at the position.
|
||||||
|
/// Otherwise returns a vector of `ReferenceSearchResult`, usually with one element.
|
||||||
|
/// Multiple results can occur in case of ambiguity or when searching for trait items.
|
||||||
|
///
|
||||||
|
/// # Special cases
|
||||||
|
/// - Control flow keywords (break, continue, etc): Shows all related jump points
|
||||||
|
/// - Constructor search: When on struct/enum definition tokens (`{`, `(`, `;`), shows only initialization sites
|
||||||
|
/// - Format string arguments: Shows template parameter usages
|
||||||
|
/// - Lifetime parameters: Shows lifetime constraint usages
|
||||||
|
///
|
||||||
|
/// # Constructor search
|
||||||
|
/// When the cursor is on specific tokens in a struct/enum definition:
|
||||||
|
/// - `{` after struct/enum/variant: Shows record literal initializations
|
||||||
|
/// - `(` after tuple struct/variant: Shows tuple literal initializations
|
||||||
|
/// - `;` after unit struct: Shows unit literal initializations
|
||||||
|
/// - Type name in definition: Shows all initialization usages
|
||||||
|
/// In these cases, other kinds of references (like type references) are filtered out.
|
||||||
pub(crate) fn find_all_refs(
|
pub(crate) fn find_all_refs(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
@ -143,7 +200,7 @@ pub(crate) fn find_defs(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some((_, resolution)) = sema.check_for_format_args_template(token.clone(), offset) {
|
if let Some((.., resolution)) = sema.check_for_format_args_template(token.clone(), offset) {
|
||||||
return resolution.map(Definition::from).map(|it| vec![it]);
|
return resolution.map(Definition::from).map(|it| vec![it]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +276,19 @@ fn retain_adt_literal_usages(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some` if the cursor is at a position for an item to search for all its constructor/literal usages
|
/// Returns `Some` if the cursor is at a position where we should search for constructor/initialization usages.
|
||||||
|
/// This is used to implement the special constructor search behavior when the cursor is on specific tokens
|
||||||
|
/// in a struct/enum/variant definition.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Some(name)` if the cursor is on:
|
||||||
|
/// - `{` after a struct/enum/variant definition
|
||||||
|
/// - `(` for tuple structs/variants
|
||||||
|
/// - `;` for unit structs
|
||||||
|
/// - The type name in a struct/enum/variant definition
|
||||||
|
/// - `None` otherwise
|
||||||
|
///
|
||||||
|
/// The returned name is the name of the type whose constructor usages should be searched for.
|
||||||
fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
|
fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
|
||||||
let token = syntax.token_at_offset(position.offset).right_biased()?;
|
let token = syntax.token_at_offset(position.offset).right_biased()?;
|
||||||
let token_parent = token.parent()?;
|
let token_parent = token.parent()?;
|
||||||
@ -257,6 +326,16 @@ fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if a name reference is part of an enum variant literal expression.
|
||||||
|
/// Used to filter references when searching for enum variant constructors.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `sema` - Semantic analysis context
|
||||||
|
/// * `enum_` - The enum type to check against
|
||||||
|
/// * `name_ref` - The name reference to check
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the name reference is used as part of constructing a variant of the given enum.
|
||||||
fn is_enum_lit_name_ref(
|
fn is_enum_lit_name_ref(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
enum_: hir::Enum,
|
enum_: hir::Enum,
|
||||||
@ -284,12 +363,19 @@ fn is_enum_lit_name_ref(
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if a path ends with the given name reference.
|
||||||
|
/// Helper function for checking constructor usage patterns.
|
||||||
fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
|
fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
|
||||||
path.and_then(|path| path.segment())
|
path.and_then(|path| path.segment())
|
||||||
.and_then(|segment| segment.name_ref())
|
.and_then(|segment| segment.name_ref())
|
||||||
.map_or(false, |segment| segment == *name_ref)
|
.map_or(false, |segment| segment == *name_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if a name reference is used in a literal (constructor) context.
|
||||||
|
/// Used to filter references when searching for struct/variant constructors.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the name reference is used as part of a struct/variant literal expression.
|
||||||
fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
|
fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
|
||||||
name_ref.syntax().ancestors().find_map(|ancestor| {
|
name_ref.syntax().ancestors().find_map(|ancestor| {
|
||||||
match_ast! {
|
match_ast! {
|
||||||
|
@ -203,7 +203,7 @@ fn find_definitions(
|
|||||||
) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> {
|
) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> {
|
||||||
let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));
|
let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));
|
||||||
|
|
||||||
if let Some((range, Some(resolution))) =
|
if let Some((range, _, _, Some(resolution))) =
|
||||||
token.and_then(|token| sema.check_for_format_args_template(token, offset))
|
token.and_then(|token| sema.check_for_format_args_template(token, offset))
|
||||||
{
|
{
|
||||||
return Ok(vec![(
|
return Ok(vec![(
|
||||||
|
@ -542,7 +542,7 @@ fn descend_token(
|
|||||||
|
|
||||||
let mut t = None;
|
let mut t = None;
|
||||||
let mut r = 0;
|
let mut r = 0;
|
||||||
sema.descend_into_macros_breakable(token.clone(), |tok, _ctx| {
|
sema.descend_into_macros_breakable(token.clone().into(), |tok, _ctx| {
|
||||||
// FIXME: Consider checking ctx transparency for being opaque?
|
// FIXME: Consider checking ctx transparency for being opaque?
|
||||||
let my_rank = ranker.rank_token(&tok.value);
|
let my_rank = ranker.rank_token(&tok.value);
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ fn check_(
|
|||||||
"{}",
|
"{}",
|
||||||
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
|
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
|
||||||
node.syntax_node(),
|
node.syntax_node(),
|
||||||
&mut |it| it.clone()
|
&mut |_| None,
|
||||||
|
|_| ()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
expect.assert_eq(&expect_res);
|
expect.assert_eq(&expect_res);
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use ide_db::base_db::{
|
use ide_db::base_db::{
|
||||||
DbPanicContext,
|
DbPanicContext,
|
||||||
salsa::{self, Cancelled},
|
salsa::{self, Cancelled, UnexpectedCycle},
|
||||||
};
|
};
|
||||||
use lsp_server::{ExtractError, Response, ResponseError};
|
use lsp_server::{ExtractError, Response, ResponseError};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
@ -349,11 +349,14 @@ where
|
|||||||
let mut message = "request handler panicked".to_owned();
|
let mut message = "request handler panicked".to_owned();
|
||||||
if let Some(panic_message) = panic_message {
|
if let Some(panic_message) = panic_message {
|
||||||
message.push_str(": ");
|
message.push_str(": ");
|
||||||
message.push_str(panic_message)
|
message.push_str(panic_message);
|
||||||
|
} else if let Some(cycle) = panic.downcast_ref::<UnexpectedCycle>() {
|
||||||
|
tracing::error!("{cycle}");
|
||||||
|
message.push_str(": unexpected cycle");
|
||||||
} else if let Ok(cancelled) = panic.downcast::<Cancelled>() {
|
} else if let Ok(cancelled) = panic.downcast::<Cancelled>() {
|
||||||
tracing::error!("Cancellation propagated out of salsa! This is a bug");
|
tracing::error!("Cancellation propagated out of salsa! This is a bug");
|
||||||
return Err(HandlerCancelledError::Inner(*cancelled));
|
return Err(HandlerCancelledError::Inner(*cancelled));
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(lsp_server::Response::new_err(
|
Ok(lsp_server::Response::new_err(
|
||||||
id,
|
id,
|
||||||
|
@ -103,6 +103,7 @@ pub(crate) fn file_range_uri(
|
|||||||
|
|
||||||
pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
|
pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
|
||||||
let assist_kind = match &kind {
|
let assist_kind = match &kind {
|
||||||
|
k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::Generate,
|
||||||
k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
|
k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
|
||||||
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
|
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
|
||||||
k if k == &lsp_types::CodeActionKind::REFACTOR_EXTRACT => AssistKind::RefactorExtract,
|
k if k == &lsp_types::CodeActionKind::REFACTOR_EXTRACT => AssistKind::RefactorExtract,
|
||||||
|
@ -112,7 +112,10 @@ pub struct EditionedFileId(u32);
|
|||||||
|
|
||||||
impl fmt::Debug for EditionedFileId {
|
impl fmt::Debug for EditionedFileId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_tuple("EditionedFileId").field(&self.file_id()).field(&self.edition()).finish()
|
f.debug_tuple("EditionedFileId")
|
||||||
|
.field(&self.file_id().index())
|
||||||
|
.field(&self.edition())
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,13 @@ use syntax::{
|
|||||||
ted::{self, Position},
|
ted::{self, Position},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PrettifyWsKind {
|
||||||
|
Space,
|
||||||
|
Indent(usize),
|
||||||
|
Newline,
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
|
/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
|
||||||
///
|
///
|
||||||
/// This is an internal API that is only exported because `mbe` needs it for tests and cannot depend
|
/// This is an internal API that is only exported because `mbe` needs it for tests and cannot depend
|
||||||
@ -15,7 +22,8 @@ use syntax::{
|
|||||||
#[deprecated = "use `hir_expand::prettify_macro_expansion()` instead"]
|
#[deprecated = "use `hir_expand::prettify_macro_expansion()` instead"]
|
||||||
pub fn prettify_macro_expansion(
|
pub fn prettify_macro_expansion(
|
||||||
syn: SyntaxNode,
|
syn: SyntaxNode,
|
||||||
dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> SyntaxToken,
|
dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> Option<SyntaxToken>,
|
||||||
|
inspect_mods: impl FnOnce(&[(Position, PrettifyWsKind)]),
|
||||||
) -> SyntaxNode {
|
) -> SyntaxNode {
|
||||||
let mut indent = 0;
|
let mut indent = 0;
|
||||||
let mut last: Option<SyntaxKind> = None;
|
let mut last: Option<SyntaxKind> = None;
|
||||||
@ -27,14 +35,12 @@ pub fn prettify_macro_expansion(
|
|||||||
let after = Position::after;
|
let after = Position::after;
|
||||||
|
|
||||||
let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
|
let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
|
||||||
(pos(token.clone()), make::tokens::whitespace(&" ".repeat(4 * indent)))
|
(pos(token.clone()), PrettifyWsKind::Indent(indent))
|
||||||
};
|
|
||||||
let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
|
|
||||||
(pos(token.clone()), make::tokens::single_space())
|
|
||||||
};
|
|
||||||
let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
|
|
||||||
(pos(token.clone()), make::tokens::single_newline())
|
|
||||||
};
|
};
|
||||||
|
let do_ws =
|
||||||
|
|pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Space);
|
||||||
|
let do_nl =
|
||||||
|
|pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Newline);
|
||||||
|
|
||||||
for event in syn.preorder_with_tokens() {
|
for event in syn.preorder_with_tokens() {
|
||||||
let token = match event {
|
let token = match event {
|
||||||
@ -46,20 +52,19 @@ pub fn prettify_macro_expansion(
|
|||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
if indent > 0 {
|
if indent > 0 {
|
||||||
mods.push((
|
mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent)));
|
||||||
Position::after(node.clone()),
|
|
||||||
make::tokens::whitespace(&" ".repeat(4 * indent)),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if node.parent().is_some() {
|
if node.parent().is_some() {
|
||||||
mods.push((Position::after(node), make::tokens::single_newline()));
|
mods.push((Position::after(node), PrettifyWsKind::Newline));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
if token.kind() == SyntaxKind::IDENT && token.text() == "$crate" {
|
if token.kind() == SyntaxKind::IDENT && token.text() == "$crate" {
|
||||||
dollar_crate_replacements.push((token.clone(), dollar_crate_replacement(&token)));
|
if let Some(replacement) = dollar_crate_replacement(&token) {
|
||||||
|
dollar_crate_replacements.push((token.clone(), replacement));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let tok = &token;
|
let tok = &token;
|
||||||
|
|
||||||
@ -129,8 +134,16 @@ pub fn prettify_macro_expansion(
|
|||||||
last = Some(tok.kind());
|
last = Some(tok.kind());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inspect_mods(&mods);
|
||||||
for (pos, insert) in mods {
|
for (pos, insert) in mods {
|
||||||
ted::insert(pos, insert);
|
ted::insert_raw(
|
||||||
|
pos,
|
||||||
|
match insert {
|
||||||
|
PrettifyWsKind::Space => make::tokens::single_space(),
|
||||||
|
PrettifyWsKind::Indent(indent) => make::tokens::whitespace(&" ".repeat(4 * indent)),
|
||||||
|
PrettifyWsKind::Newline => make::tokens::single_newline(),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (old, new) in dollar_crate_replacements {
|
for (old, new) in dollar_crate_replacements {
|
||||||
ted::replace(old, new);
|
ted::replace(old, new);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use std::{mem, ops::RangeInclusive};
|
use std::{mem, ops::RangeInclusive};
|
||||||
|
|
||||||
use parser::T;
|
use parser::T;
|
||||||
|
use rowan::TextSize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
|
SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
|
||||||
@ -74,6 +75,12 @@ impl Position {
|
|||||||
};
|
};
|
||||||
Position { repr }
|
Position { repr }
|
||||||
}
|
}
|
||||||
|
pub fn offset(&self) -> TextSize {
|
||||||
|
match &self.repr {
|
||||||
|
PositionRepr::FirstChild(node) => node.text_range().start(),
|
||||||
|
PositionRepr::After(elem) => elem.text_range().end(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(position: Position, elem: impl Element) {
|
pub fn insert(position: Position, elem: impl Element) {
|
||||||
@ -207,5 +214,12 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
|
|||||||
}
|
}
|
||||||
return Some(make::tokens::whitespace(&format!("\n{indent}")));
|
return Some(make::tokens::whitespace(&format!("\n{indent}")));
|
||||||
}
|
}
|
||||||
|
if left.kind() == SyntaxKind::ATTR {
|
||||||
|
let mut indent = IndentLevel::from_element(right);
|
||||||
|
if right.kind() == SyntaxKind::ATTR {
|
||||||
|
indent.0 = IndentLevel::from_element(left).0.max(indent.0);
|
||||||
|
}
|
||||||
|
return Some(make::tokens::whitespace(&format!("\n{indent}")));
|
||||||
|
}
|
||||||
Some(make::tokens::single_space())
|
Some(make::tokens::single_space())
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,13 @@ impl VfsPath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_abs_path(self) -> Option<AbsPathBuf> {
|
||||||
|
match self.0 {
|
||||||
|
VfsPathRepr::PathBuf(it) => Some(it),
|
||||||
|
VfsPathRepr::VirtualPath(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new `VfsPath` with `path` adjoined to `self`.
|
/// Creates a new `VfsPath` with `path` adjoined to `self`.
|
||||||
pub fn join(&self, path: &str) -> Option<VfsPath> {
|
pub fn join(&self, path: &str) -> Option<VfsPath> {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
|
@ -24,8 +24,8 @@ impl flags::Codegen {
|
|||||||
grammar::generate(self.check);
|
grammar::generate(self.check);
|
||||||
assists_doc_tests::generate(self.check);
|
assists_doc_tests::generate(self.check);
|
||||||
parser_inline_tests::generate(self.check);
|
parser_inline_tests::generate(self.check);
|
||||||
feature_docs::generate(self.check)
|
feature_docs::generate(self.check);
|
||||||
// diagnostics_docs::generate(self.check) doesn't generate any tests
|
diagnostics_docs::generate(self.check);
|
||||||
// lints::generate(self.check) Updating clones the rust repo, so don't run it unless
|
// lints::generate(self.check) Updating clones the rust repo, so don't run it unless
|
||||||
// explicitly asked for
|
// explicitly asked for
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user