Stop expanding UseTrees during ItemTree lowering

This commit is contained in:
Jonas Schievink 2021-05-26 01:01:58 +02:00
parent 5587d0a3e3
commit b52df91877
12 changed files with 320 additions and 188 deletions

View File

@ -472,27 +472,13 @@ pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
});
}
DefDiagnosticKind::UnresolvedImport { ast, index } => {
let use_item = ast.to_node(db.upcast());
let hygiene = Hygiene::new(db.upcast(), ast.file_id);
let mut cur = 0;
let mut tree = None;
ModPath::expand_use_item(
db.upcast(),
InFile::new(ast.file_id, use_item),
&hygiene,
|_mod_path, use_tree, _is_glob, _alias| {
if cur == *index {
tree = Some(use_tree.clone());
}
DefDiagnosticKind::UnresolvedImport { id, index } => {
let file_id = id.file_id();
let item_tree = id.item_tree(db.upcast());
let import = &item_tree[id.value];
cur += 1;
},
);
if let Some(tree) = tree {
sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
}
let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
sink.push(UnresolvedImport { file: file_id, node: AstPtr::new(&use_tree) });
}
DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {

View File

@ -523,21 +523,38 @@ fn index(&self, id: FileItemTreeId<N>) -> &N {
}
}
/// A desugared `use` import.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Import {
pub path: Interned<ModPath>,
pub alias: Option<ImportAlias>,
pub visibility: RawVisibilityId,
pub is_glob: bool,
/// AST ID of the `use` item this import was derived from. Note that many `Import`s can map to
/// the same `use` item.
pub ast_id: FileAstId<ast::Use>,
/// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`.
///
/// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting
/// precise diagnostics.
pub index: usize,
pub use_tree: UseTree,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UseTree {
pub index: Idx<ast::UseTree>,
kind: UseTreeKind,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum UseTreeKind {
/// ```ignore
/// use path::to::Item;
/// use path::to::Item as Renamed;
/// use path::to::Trait as _;
/// ```
Single { path: ModPath, alias: Option<ImportAlias> },
/// ```ignore
/// use *; // (invalid, but can occur in nested tree)
/// use path::*;
/// ```
Glob { path: Option<ModPath> },
/// ```ignore
/// use prefix::{self, Item, ...};
/// ```
Prefixed { prefix: Option<ModPath>, list: Vec<UseTree> },
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -711,6 +728,97 @@ pub struct MacroDef {
pub ast_id: FileAstId<ast::MacroDef>,
}
impl Import {
/// Maps a `UseTree` contained in this import back to its AST node.
pub fn use_tree_to_ast(
&self,
db: &dyn DefDatabase,
file_id: HirFileId,
index: Idx<ast::UseTree>,
) -> ast::UseTree {
// Re-lower the AST item and get the source map.
// Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
let hygiene = Hygiene::new(db.upcast(), file_id);
let (_, source_map) =
lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree");
source_map[index].clone()
}
}
impl UseTree {
/// Expands the `UseTree` into individually imported `ModPath`s.
pub fn expand(
&self,
mut cb: impl FnMut(Idx<ast::UseTree>, ModPath, /* is_glob */ bool, Option<ImportAlias>),
) {
self.expand_impl(None, &mut cb)
}
fn expand_impl(
&self,
prefix: Option<ModPath>,
cb: &mut dyn FnMut(
Idx<ast::UseTree>,
ModPath,
/* is_glob */ bool,
Option<ImportAlias>,
),
) {
fn concat_mod_paths(prefix: Option<ModPath>, path: &ModPath) -> Option<ModPath> {
match (prefix, &path.kind) {
(None, _) => Some(path.clone()),
(Some(mut prefix), PathKind::Plain) => {
for segment in path.segments() {
prefix.push_segment(segment.clone());
}
Some(prefix)
}
(Some(prefix), PathKind::Super(0)) => {
// `some::path::self` == `some::path`
if path.segments().is_empty() {
Some(prefix)
} else {
None
}
}
(Some(_), _) => None,
}
}
match &self.kind {
UseTreeKind::Single { path, alias } => {
if let Some(path) = concat_mod_paths(prefix, path) {
cb(self.index, path, false, alias.clone());
}
}
UseTreeKind::Glob { path: Some(path) } => {
if let Some(path) = concat_mod_paths(prefix, path) {
cb(self.index, path, true, None);
}
}
UseTreeKind::Glob { path: None } => {
if let Some(prefix) = prefix {
cb(self.index, prefix, true, None);
}
}
UseTreeKind::Prefixed { prefix: additional_prefix, list } => {
let prefix = match additional_prefix {
Some(path) => match concat_mod_paths(prefix, path) {
Some(path) => Some(path),
None => return,
},
None => prefix,
};
for tree in list {
tree.expand_impl(prefix.clone(), cb);
}
}
}
}
}
macro_rules! impl_froms {
($e:ident { $($v:ident ($t:ty)),* $(,)? }) => {
$(

View File

@ -35,7 +35,6 @@ pub(super) struct Ctx<'a> {
db: &'a dyn DefDatabase,
tree: ItemTree,
hygiene: Hygiene,
file: HirFileId,
source_ast_id_map: Arc<AstIdMap>,
body_ctx: crate::body::LowerCtx<'a>,
forced_visibility: Option<RawVisibilityId>,
@ -47,7 +46,6 @@ pub(super) fn new(db: &'a dyn DefDatabase, hygiene: Hygiene, file: HirFileId) ->
db,
tree: ItemTree::default(),
hygiene,
file,
source_ast_id_map: db.ast_id_map(file),
body_ctx: crate::body::LowerCtx::new(db, file),
forced_visibility: None,
@ -561,30 +559,13 @@ fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> {
Some(id(self.data().impls.alloc(res)))
}
fn lower_use(&mut self, use_item: &ast::Use) -> Vec<FileItemTreeId<Import>> {
fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> {
let visibility = self.lower_visibility(use_item);
let ast_id = self.source_ast_id_map.ast_id(use_item);
let (use_tree, _) = lower_use_tree(self.db, &self.hygiene, use_item.use_tree()?)?;
// Every use item can expand to many `Import`s.
let mut imports = Vec::new();
let tree = self.tree.data_mut();
ModPath::expand_use_item(
self.db,
InFile::new(self.file, use_item.clone()),
&self.hygiene,
|path, _use_tree, is_glob, alias| {
imports.push(id(tree.imports.alloc(Import {
path: Interned::new(path),
alias,
visibility,
is_glob,
ast_id,
index: imports.len(),
})));
},
);
imports
let res = Import { visibility, ast_id, use_tree };
Some(id(self.data().imports.alloc(res)))
}
fn lower_extern_crate(
@ -884,3 +865,76 @@ fn lower_abi(abi: ast::Abi) -> Interned<str> {
}
}
}
struct UseTreeLowering<'a> {
db: &'a dyn DefDatabase,
hygiene: &'a Hygiene,
mapping: Arena<ast::UseTree>,
}
impl UseTreeLowering<'_> {
fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> {
if let Some(use_tree_list) = tree.use_tree_list() {
let prefix = match tree.path() {
// E.g. use something::{{{inner}}};
None => None,
// E.g. `use something::{inner}` (prefix is `None`, path is `something`)
// or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
Some(path) => {
match ModPath::from_src(self.db, path, &self.hygiene) {
Some(it) => Some(it),
None => return None, // FIXME: report errors somewhere
}
}
};
let list =
use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect();
Some(self.use_tree(UseTreeKind::Prefixed { prefix, list }, tree))
} else {
let is_glob = tree.star_token().is_some();
let path = match tree.path() {
Some(path) => Some(ModPath::from_src(self.db, path, &self.hygiene)?),
None => None,
};
let alias = tree.rename().map(|a| {
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
});
if alias.is_some() && is_glob {
return None;
}
match (path, alias, is_glob) {
(path, None, true) => {
if path.is_none() {
cov_mark::hit!(glob_enum_group);
}
Some(self.use_tree(UseTreeKind::Glob { path }, tree))
}
// Globs can't be renamed
(_, Some(_), true) | (None, None, false) => None,
// `bla::{ as Name}` is invalid
(None, Some(_), false) => None,
(Some(path), alias, false) => {
Some(self.use_tree(UseTreeKind::Single { path, alias }, tree))
}
}
}
}
fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree {
let index = self.mapping.alloc(ast);
UseTree { index, kind }
}
}
pub(super) fn lower_use_tree(
db: &dyn DefDatabase,
hygiene: &Hygiene,
tree: ast::UseTree,
) -> Option<(UseTree, Arena<ast::UseTree>)> {
let mut lowering = UseTreeLowering { db, hygiene, mapping: Arena::new() };
let tree = lowering.lower_use_tree(tree)?;
Some((tree, lowering.mapping))
}

View File

@ -163,21 +163,46 @@ fn print_fields_and_where_clause(&mut self, fields: &Fields, params: &GenericPar
}
}
fn print_use_tree(&mut self, use_tree: &UseTree) {
match &use_tree.kind {
UseTreeKind::Single { path, alias } => {
w!(self, "{}", path);
if let Some(alias) = alias {
w!(self, " as {}", alias);
}
}
UseTreeKind::Glob { path } => {
if let Some(path) = path {
w!(self, "{}::", path);
}
w!(self, "*");
}
UseTreeKind::Prefixed { prefix, list } => {
if let Some(prefix) = prefix {
w!(self, "{}::", prefix);
}
w!(self, "{{");
for (i, tree) in list.iter().enumerate() {
if i != 0 {
w!(self, ", ");
}
self.print_use_tree(tree);
}
w!(self, "}}");
}
}
}
fn print_mod_item(&mut self, item: ModItem) {
self.print_attrs_of(item);
match item {
ModItem::Import(it) => {
let Import { visibility, path, is_glob, alias, ast_id: _, index } = &self.tree[it];
let Import { visibility, use_tree, ast_id: _ } = &self.tree[it];
self.print_visibility(*visibility);
w!(self, "use {}", path);
if *is_glob {
w!(self, "::*");
}
if let Some(alias) = alias {
w!(self, " as {}", alias);
}
wln!(self, "; // {}", index);
w!(self, "use ");
self.print_use_tree(use_tree);
wln!(self, ";");
}
ModItem::ExternCrate(it) => {
let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it];

View File

@ -26,6 +26,8 @@ fn imports() {
/// docs on import
use crate::{A, B};
use a::{c, d::{e}};
"#,
expect![[r##"
#![doc = " file comment"] // AttrId { is_doc_comment: true, ast_index: 0 }
@ -36,19 +38,14 @@ fn imports() {
pub(super) extern crate bli;
pub use crate::path::nested; // 0
pub use crate::path::{nested, items as renamed, Trait as _};
pub use crate::path::items as renamed; // 1
pub use crate::path::Trait as _; // 2
pub(self) use globs::*; // 0
pub(self) use globs::*;
#[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 }
pub(self) use crate::A; // 0
pub(self) use crate::{A, B};
#[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 }
pub(self) use crate::B; // 1
pub(self) use a::{c, d::{e}};
"##]],
);
}
@ -218,7 +215,7 @@ fn fn_in_module() {}
#[doc = " outer"] // AttrId { is_doc_comment: true, ast_index: 0 }
#[doc = " inner"] // AttrId { is_doc_comment: true, ast_index: 1 }
pub(self) mod inline {
pub(self) use super::*; // 0
pub(self) use super::*;
// flags = 0x2
pub(self) fn fn_in_module() -> ();

View File

@ -17,6 +17,7 @@
};
use hir_expand::{InFile, MacroCallLoc};
use itertools::Itertools;
use la_arena::Idx;
use rustc_hash::{FxHashMap, FxHashSet};
use syntax::ast;
@ -143,7 +144,7 @@ fn namespaces(&self) -> PerNs {
#[derive(Clone, Debug, Eq, PartialEq)]
enum ImportSource {
Import(ItemTreeId<item_tree::Import>),
Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> },
ExternCrate(ItemTreeId<item_tree::ExternCrate>),
}
@ -165,20 +166,26 @@ fn from_use(
krate: CrateId,
tree: &ItemTree,
id: ItemTreeId<item_tree::Import>,
) -> Self {
) -> Vec<Self> {
let it = &tree[id.value];
let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into());
let visibility = &tree[it.visibility];
Self {
path: it.path.clone(),
alias: it.alias.clone(),
visibility: visibility.clone(),
is_glob: it.is_glob,
is_prelude: attrs.by_key("prelude_import").exists(),
is_extern_crate: false,
is_macro_use: false,
source: ImportSource::Import(id),
}
let is_prelude = attrs.by_key("prelude_import").exists();
let mut res = Vec::new();
it.use_tree.expand(|idx, path, is_glob, alias| {
res.push(Self {
path: Interned::new(path), // FIXME this makes little sense
alias,
visibility: visibility.clone(),
is_glob,
is_prelude,
is_extern_crate: false,
is_macro_use: false,
source: ImportSource::Import { id, use_tree: idx },
});
});
res
}
fn from_extern_crate(
@ -1130,11 +1137,8 @@ fn finish(mut self) -> DefMap {
}
for directive in &self.unresolved_imports {
if let ImportSource::Import(import) = &directive.import.source {
let item_tree = import.item_tree(self.db);
let import_data = &item_tree[import.value];
match (import_data.path.segments().first(), &import_data.path.kind) {
if let ImportSource::Import { id: import, use_tree } = &directive.import.source {
match (directive.import.path.segments().first(), &directive.import.path.kind) {
(Some(krate), PathKind::Plain) | (Some(krate), PathKind::Abs) => {
if diagnosed_extern_crates.contains(krate) {
continue;
@ -1145,8 +1149,8 @@ fn finish(mut self) -> DefMap {
self.def_map.diagnostics.push(DefDiagnostic::unresolved_import(
directive.module_id,
InFile::new(import.file_id(), import_data.ast_id),
import_data.index,
*import,
*use_tree,
));
}
}
@ -1222,16 +1226,20 @@ fn collect(&mut self, items: &[ModItem]) {
match item {
ModItem::Mod(m) => self.collect_module(&self.item_tree[m], &attrs),
ModItem::Import(import_id) => {
self.def_collector.unresolved_imports.push(ImportDirective {
module_id: self.module_id,
import: Import::from_use(
self.def_collector.db,
krate,
&self.item_tree,
ItemTreeId::new(self.file_id, import_id),
),
status: PartialResolvedImport::Unresolved,
})
let module_id = self.module_id;
let imports = Import::from_use(
self.def_collector.db,
krate,
&self.item_tree,
ItemTreeId::new(self.file_id, import_id),
);
self.def_collector.unresolved_imports.extend(imports.into_iter().map(
|import| ImportDirective {
module_id,
import,
status: PartialResolvedImport::Unresolved,
},
));
}
ModItem::ExternCrate(import_id) => {
self.def_collector.unresolved_imports.push(ImportDirective {

View File

@ -2,9 +2,15 @@
use cfg::{CfgExpr, CfgOptions};
use hir_expand::MacroCallKind;
use la_arena::Idx;
use syntax::ast;
use crate::{nameres::LocalModuleId, path::ModPath, AstId};
use crate::{
item_tree::{self, ItemTreeId},
nameres::LocalModuleId,
path::ModPath,
AstId,
};
#[derive(Debug, PartialEq, Eq)]
pub enum DefDiagnosticKind {
@ -12,7 +18,7 @@ pub enum DefDiagnosticKind {
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> },
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
@ -53,10 +59,10 @@ pub(super) fn unresolved_extern_crate(
pub(super) fn unresolved_import(
container: LocalModuleId,
ast: AstId<ast::Use>,
index: usize,
id: ItemTreeId<item_tree::Import>,
index: Idx<ast::UseTree>,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } }
}
pub(super) fn unconfigured_code(

View File

@ -14,10 +14,7 @@
};
use syntax::ast;
use crate::{
type_ref::{TypeBound, TypeRef},
InFile,
};
use crate::type_ref::{TypeBound, TypeRef};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModPath {
@ -56,8 +53,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl ModPath {
pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> {
let ctx = LowerCtx::with_hygiene(db, hygiene);
lower::lower_path(path, &ctx).map(|it| (*it.mod_path).clone())
lower::convert_path(db, None, path, hygiene)
}
pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath {
@ -70,18 +66,6 @@ pub const fn from_kind(kind: PathKind) -> ModPath {
ModPath { kind, segments: Vec::new() }
}
/// Calls `cb` with all paths, represented by this use item.
pub fn expand_use_item(
db: &dyn DefDatabase,
item_src: InFile<ast::Use>,
hygiene: &Hygiene,
mut cb: impl FnMut(ModPath, &ast::UseTree, /* is_glob */ bool, Option<ImportAlias>),
) {
if let Some(tree) = item_src.value.use_tree() {
lower::lower_use_tree(db, None, tree, hygiene, &mut cb);
}
}
pub fn segments(&self) -> &[Name] {
&self.segments
}

View File

@ -15,7 +15,7 @@
type_ref::{LifetimeRef, TypeBound, TypeRef},
};
pub(super) use lower_use::lower_use_tree;
pub(super) use lower_use::convert_path;
/// Converts an `ast::Path` to `Path`. Works with use trees.
/// It correctly handles `$crate` based path from macro call.

View File

@ -4,68 +4,15 @@
use std::iter;
use either::Either;
use hir_expand::{hygiene::Hygiene, name::AsName};
use syntax::ast::{self, NameOwner};
use hir_expand::hygiene::Hygiene;
use syntax::{ast, AstNode};
use crate::{
db::DefDatabase,
path::{ImportAlias, ModPath, PathKind},
path::{ModPath, PathKind},
};
pub(crate) fn lower_use_tree(
db: &dyn DefDatabase,
prefix: Option<ModPath>,
tree: ast::UseTree,
hygiene: &Hygiene,
cb: &mut dyn FnMut(ModPath, &ast::UseTree, bool, Option<ImportAlias>),
) {
if let Some(use_tree_list) = tree.use_tree_list() {
let prefix = match tree.path() {
// E.g. use something::{{{inner}}};
None => prefix,
// E.g. `use something::{inner}` (prefix is `None`, path is `something`)
// or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
Some(path) => match convert_path(db, prefix, path, hygiene) {
Some(it) => Some(it),
None => return, // FIXME: report errors somewhere
},
};
for child_tree in use_tree_list.use_trees() {
lower_use_tree(db, prefix.clone(), child_tree, hygiene, cb);
}
} else {
let alias = tree.rename().map(|a| {
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
});
let is_glob = tree.star_token().is_some();
if let Some(ast_path) = tree.path() {
// Handle self in a path.
// E.g. `use something::{self, <...>}`
if ast_path.qualifier().is_none() {
if let Some(segment) = ast_path.segment() {
if segment.kind() == Some(ast::PathSegmentKind::SelfKw) {
if let Some(prefix) = prefix {
cb(prefix, &tree, false, alias);
return;
}
}
}
}
if let Some(path) = convert_path(db, prefix, ast_path, hygiene) {
cb(path, &tree, is_glob, alias)
}
// FIXME: report errors somewhere
// We get here if we do
} else if is_glob {
cov_mark::hit!(glob_enum_group);
if let Some(prefix) = prefix {
cb(prefix, &tree, is_glob, None)
}
}
}
}
fn convert_path(
pub(crate) fn convert_path(
db: &dyn DefDatabase,
prefix: Option<ModPath>,
path: ast::Path,
@ -78,7 +25,7 @@ fn convert_path(
};
let segment = path.segment()?;
let res = match segment.kind()? {
let mut mod_path = match segment.kind()? {
ast::PathSegmentKind::Name(name_ref) => {
match hygiene.name_ref_to_name(db.upcast(), name_ref) {
Either::Left(name) => {
@ -125,5 +72,18 @@ fn convert_path(
return None;
}
};
Some(res)
// handle local_inner_macros :
// Basically, even in rustc it is quite hacky:
// https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456
// We follow what it did anyway :)
if mod_path.segments.len() == 1 && mod_path.kind == PathKind::Plain {
if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
if let Some(crate_id) = hygiene.local_inner_macros(db.upcast(), path) {
mod_path.kind = PathKind::DollarCrate(crate_id);
}
}
}
Some(mod_path)
}

View File

@ -278,9 +278,11 @@ pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
}
DefDiagnosticKind::UnresolvedImport { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
DefDiagnosticKind::UnresolvedImport { id, .. } => {
let item_tree = id.item_tree(self.upcast());
let import = &item_tree[id.value];
let node = InFile::new(id.file_id(), import.ast_id).to_node(self.upcast());
(InFile::new(id.file_id(), node.syntax().clone()), "UnresolvedImport")
}
DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
let node = ast.to_node(self.upcast());

View File

@ -311,6 +311,7 @@ mod tests {
/// * a diagnostic is produced
/// * the first diagnostic fix trigger range touches the input cursor position
/// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
#[track_caller]
pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
check_nth_fix(0, ra_fixture_before, ra_fixture_after);
}
@ -325,6 +326,7 @@ pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>)
}
}
#[track_caller]
fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
let after = trim_indent(ra_fixture_after);