diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ac536d0fdd..dc0a6c2d91 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -36,7 +36,6 @@ jobs: - os: ubuntu-20.04 target: x86_64-unknown-linux-gnu code-target: linux-x64 - container: ubuntu:18.04 - os: ubuntu-20.04 target: aarch64-unknown-linux-gnu code-target: linux-arm64 @@ -63,14 +62,6 @@ jobs: with: fetch-depth: ${{ env.FETCH_DEPTH }} - - name: Install toolchain dependencies - if: matrix.container == 'ubuntu:18.04' - shell: bash - run: | - apt-get update && apt-get install -y build-essential curl - curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL "https://sh.rustup.rs" | sh -s -- --profile minimal --default-toolchain none -y - echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH - - name: Install Rust toolchain run: | rustup update --no-self-update stable diff --git a/Cargo.lock b/Cargo.lock index 903141eee9..68ed32391b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,7 @@ version = "0.0.0" dependencies = [ "cfg", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lz4_flex", "rustc-hash", "salsa", "semver", @@ -134,9 +135,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg" @@ -874,9 +875,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", "windows-targets 0.52.4", @@ -992,6 +993,12 @@ dependencies = [ "url", ] +[[package]] +name = "lz4_flex" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" + [[package]] name = "mbe" version = "0.0.0" @@ -1597,6 +1604,7 @@ dependencies = [ "rayon", "rustc-hash", "scip", + "semver", "serde", "serde_json", "sourcegen", diff --git a/Cargo.toml b/Cargo.toml index 440f46a938..0679522efd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,10 @@ anyhow = "1.0.75" arrayvec = "0.7.4" bitflags = "2.4.1" cargo_metadata = "0.18.1" +chalk-solve = { version = "0.96.0", default-features = false } +chalk-ir = "0.96.0" +chalk-recursive = { version = "0.96.0", default-features = false } +chalk-derive = "0.96.0" command-group = "2.0.1" crossbeam-channel = "0.5.8" dissimilar = "1.0.7" diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 118abf5d6e..4ab99fc33c 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -12,6 +12,8 @@ rust-version.workspace = true doctest = false [dependencies] +lz4_flex = { version = "0.11", default-features = false } + la-arena.workspace = true salsa.workspace = true rustc-hash.workspace = true diff --git a/crates/base-db/src/change.rs b/crates/base-db/src/change.rs index 003ffb24d9..f202a885e2 100644 --- a/crates/base-db/src/change.rs +++ b/crates/base-db/src/change.rs @@ -7,13 +7,13 @@ use salsa::Durability; use triomphe::Arc; use vfs::FileId; -use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId}; +use crate::{CrateGraph, SourceDatabaseExt, SourceDatabaseExt2, SourceRoot, SourceRootId}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] pub struct FileChange { pub roots: Option>, - pub files_changed: Vec<(FileId, Option>)>, + pub files_changed: Vec<(FileId, Option)>, pub crate_graph: Option, } @@ -42,7 +42,7 @@ impl FileChange { self.roots = Some(roots); } - pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { + pub fn change_file(&mut self, file_id: FileId, new_text: Option) { self.files_changed.push((file_id, new_text)) } @@ -68,8 +68,8 @@ impl FileChange { let source_root = db.source_root(source_root_id); let durability = durability(&source_root); // XXX: can't actually remove the file, just reset the text - let text = text.unwrap_or_else(|| Arc::from("")); - db.set_file_text_with_durability(file_id, text, durability) + let text = text.unwrap_or_default(); + db.set_file_text_with_durability(file_id, &text, durability) } if let Some(crate_graph) = self.crate_graph { db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH); diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index 758d2a45c8..5dcb580723 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -7,6 +7,7 @@ mod input; use std::panic; +use salsa::Durability; use syntax::{ast, Parse, SourceFile}; use triomphe::Arc; @@ -42,6 +43,7 @@ pub trait Upcast { fn upcast(&self) -> &T; } +pub const DEFAULT_FILE_TEXT_LRU_CAP: usize = 16; pub const DEFAULT_PARSE_LRU_CAP: usize = 128; pub const DEFAULT_BORROWCK_LRU_CAP: usize = 1024; @@ -89,7 +91,10 @@ fn parse(db: &dyn SourceDatabase, file_id: FileId) -> Parse { #[salsa::query_group(SourceDatabaseExtStorage)] pub trait SourceDatabaseExt: SourceDatabase { #[salsa::input] + fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>; + fn file_text(&self, file_id: FileId) -> Arc; + /// Path to a file, relative to the root of its source root. /// Source root of the file. #[salsa::input] @@ -101,6 +106,44 @@ pub trait SourceDatabaseExt: SourceDatabase { fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>; } +fn file_text(db: &dyn SourceDatabaseExt, file_id: FileId) -> Arc { + let bytes = db.compressed_file_text(file_id); + let bytes = + lz4_flex::decompress_size_prepended(&bytes).expect("lz4 decompression should not fail"); + let text = std::str::from_utf8(&bytes).expect("file contents should be valid UTF-8"); + Arc::from(text) +} + +pub trait SourceDatabaseExt2 { + fn set_file_text(&mut self, file_id: FileId, text: &str) { + self.set_file_text_with_durability(file_id, text, Durability::LOW); + } + + fn set_file_text_with_durability( + &mut self, + file_id: FileId, + text: &str, + durability: Durability, + ); +} + +impl SourceDatabaseExt2 for Db { + fn set_file_text_with_durability( + &mut self, + file_id: FileId, + text: &str, + durability: Durability, + ) { + let bytes = text.as_bytes(); + let compressed = lz4_flex::compress_prepend_size(bytes); + self.set_compressed_file_text_with_durability( + file_id, + Arc::from(compressed.as_slice()), + durability, + ) + } +} + fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<[CrateId]> { let graph = db.crate_graph(); let mut crates = graph diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml index fbda065b10..9b3a5026ac 100644 --- a/crates/cfg/Cargo.toml +++ b/crates/cfg/Cargo.toml @@ -31,4 +31,4 @@ mbe.workspace = true syntax.workspace = true [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs index 4be6ae7481..b7dbb7b5fd 100644 --- a/crates/cfg/src/cfg_expr.rs +++ b/crates/cfg/src/cfg_expr.rs @@ -47,6 +47,7 @@ impl CfgExpr { pub fn parse(tt: &tt::Subtree) -> CfgExpr { next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) } + /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option { match self { @@ -62,7 +63,6 @@ impl CfgExpr { } } } - fn next_cfg_expr(it: &mut SliceIter<'_, tt::TokenTree>) -> Option { let name = match it.next() { None => return None, diff --git a/crates/flycheck/src/test_runner.rs b/crates/flycheck/src/test_runner.rs index 6dac5899ee..31378716b3 100644 --- a/crates/flycheck/src/test_runner.rs +++ b/crates/flycheck/src/test_runner.rs @@ -28,19 +28,20 @@ pub enum CargoTestMessage { }, Suite, Finished, + Custom { + text: String, + }, } impl ParseFromLine for CargoTestMessage { - fn from_line(line: &str, error: &mut String) -> Option { + fn from_line(line: &str, _: &mut String) -> Option { let mut deserializer = serde_json::Deserializer::from_str(line); deserializer.disable_recursion_limit(); if let Ok(message) = CargoTestMessage::deserialize(&mut deserializer) { return Some(message); } - error.push_str(line); - error.push('\n'); - None + Some(CargoTestMessage::Custom { text: line.to_owned() }) } fn from_eof() -> Option { diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 37d37fd331..c9f1add275 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -10,7 +10,6 @@ use std::ops::Index; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use either::Either; use hir_expand::{name::Name, HirFileId, InFile}; use la_arena::{Arena, ArenaMap}; use rustc_hash::FxHashMap; @@ -45,7 +44,8 @@ pub struct Body { /// /// If this `Body` is for the body of a constant, this will just be /// empty. - pub params: Vec, + pub params: Box<[PatId]>, + pub self_param: Option, /// The `ExprId` of the actual body expression. pub body_expr: ExprId, /// Block expressions in this body that may contain inner items. @@ -55,7 +55,7 @@ pub struct Body { pub type ExprPtr = AstPtr; pub type ExprSource = InFile; -pub type PatPtr = AstPtr>; +pub type PatPtr = AstPtr; pub type PatSource = InFile; pub type LabelPtr = AstPtr; @@ -63,6 +63,7 @@ pub type LabelSource = InFile; pub type FieldPtr = AstPtr; pub type FieldSource = InFile; + pub type PatFieldPtr = AstPtr; pub type PatFieldSource = InFile; @@ -88,6 +89,8 @@ pub struct BodySourceMap { label_map: FxHashMap, label_map_back: ArenaMap, + self_param: Option>>, + /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). /// Instead, we use id of expression (`92`) to identify the field. field_map_back: FxHashMap, @@ -215,10 +218,11 @@ impl Body { fn shrink_to_fit(&mut self) { let Self { body_expr: _, + params: _, + self_param: _, block_scopes, exprs, labels, - params, pats, bindings, binding_owners, @@ -226,7 +230,6 @@ impl Body { block_scopes.shrink_to_fit(); exprs.shrink_to_fit(); labels.shrink_to_fit(); - params.shrink_to_fit(); pats.shrink_to_fit(); bindings.shrink_to_fit(); binding_owners.shrink_to_fit(); @@ -297,6 +300,7 @@ impl Default for Body { params: Default::default(), block_scopes: Default::default(), binding_owners: Default::default(), + self_param: Default::default(), } } } @@ -354,14 +358,12 @@ impl BodySourceMap { self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax) } - pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option { - let src = node.map(|it| AstPtr::new(it).wrap_left()); - self.pat_map.get(&src).cloned() + pub fn self_param_syntax(&self) -> Option>> { + self.self_param } - pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option { - let src = node.map(|it| AstPtr::new(it).wrap_right()); - self.pat_map.get(&src).cloned() + pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option { + self.pat_map.get(&node.map(AstPtr::new)).cloned() } pub fn label_syntax(&self, label: LabelId) -> LabelSource { @@ -401,6 +403,7 @@ impl BodySourceMap { fn shrink_to_fit(&mut self) { let Self { + self_param: _, expr_map, expr_map_back, pat_map, diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 6669127789..340e95dbc2 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -4,7 +4,6 @@ use std::mem; use base_db::CrateId; -use either::Either; use hir_expand::{ name::{name, AsName, Name}, ExpandError, InFile, @@ -29,7 +28,6 @@ use crate::{ db::DefDatabase, expander::Expander, hir::{ - dummy_expr_id, format_args::{ self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, @@ -66,16 +64,7 @@ pub(super) fn lower( def_map: expander.module.def_map(db), source_map: BodySourceMap::default(), ast_id_map: db.ast_id_map(expander.current_file_id()), - body: Body { - exprs: Default::default(), - pats: Default::default(), - bindings: Default::default(), - binding_owners: Default::default(), - labels: Default::default(), - params: Vec::new(), - body_expr: dummy_expr_id(), - block_scopes: Vec::new(), - }, + body: Body::default(), expander, current_try_block_label: None, is_lowering_assignee_expr: false, @@ -191,35 +180,35 @@ impl ExprCollector<'_> { is_async_fn: bool, ) -> (Body, BodySourceMap) { if let Some((param_list, mut attr_enabled)) = param_list { + let mut params = vec![]; if let Some(self_param) = param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false)) { let is_mutable = self_param.mut_token().is_some() && self_param.amp_token().is_none(); - let ptr = AstPtr::new(&Either::Right(self_param)); let binding_id: la_arena::Idx = self.alloc_binding(name![self], BindingAnnotation::new(is_mutable, false)); - let param_pat = self.alloc_pat(Pat::Bind { id: binding_id, subpat: None }, ptr); - self.add_definition_to_binding(binding_id, param_pat); - self.body.params.push(param_pat); + self.body.self_param = Some(binding_id); + self.source_map.self_param = Some(self.expander.in_file(AstPtr::new(&self_param))); } for (param, _) in param_list.params().zip(attr_enabled).filter(|(_, enabled)| *enabled) { let param_pat = self.collect_pat_top(param.pat()); - self.body.params.push(param_pat); + params.push(param_pat); } + self.body.params = params.into_boxed_slice(); }; self.body.body_expr = self.with_label_rib(RibKind::Closure, |this| { if is_async_fn { match body { Some(e) => { + let syntax_ptr = AstPtr::new(&e); let expr = this.collect_expr(e); - this.alloc_expr_desugared(Expr::Async { - id: None, - statements: Box::new([]), - tail: Some(expr), - }) + this.alloc_expr_desugared_with_ptr( + Expr::Async { id: None, statements: Box::new([]), tail: Some(expr) }, + syntax_ptr, + ) } None => this.missing_expr(), } @@ -405,7 +394,7 @@ impl ExprCollector<'_> { } ast::Expr::ParenExpr(e) => { let inner = self.collect_expr_opt(e.expr()); - // make the paren expr point to the inner expression as well + // make the paren expr point to the inner expression as well for IDE resolution let src = self.expander.in_file(syntax_ptr); self.source_map.expr_map.insert(src, inner); inner @@ -707,6 +696,7 @@ impl ExprCollector<'_> { .alloc_label_desugared(Label { name: Name::generate_new_name(self.body.labels.len()) }); let old_label = self.current_try_block_label.replace(label); + let ptr = AstPtr::new(&e).upcast(); let (btail, expr_id) = self.with_labeled_rib(label, |this| { let mut btail = None; let block = this.collect_block_(e, |id, statements, tail| { @@ -716,23 +706,21 @@ impl ExprCollector<'_> { (btail, block) }); - let callee = self.alloc_expr_desugared(Expr::Path(try_from_output)); + let callee = self.alloc_expr_desugared_with_ptr(Expr::Path(try_from_output), ptr); let next_tail = match btail { - Some(tail) => self.alloc_expr_desugared(Expr::Call { - callee, - args: Box::new([tail]), - is_assignee_expr: false, - }), + Some(tail) => self.alloc_expr_desugared_with_ptr( + Expr::Call { callee, args: Box::new([tail]), is_assignee_expr: false }, + ptr, + ), None => { - let unit = self.alloc_expr_desugared(Expr::Tuple { - exprs: Box::new([]), - is_assignee_expr: false, - }); - self.alloc_expr_desugared(Expr::Call { - callee, - args: Box::new([unit]), - is_assignee_expr: false, - }) + let unit = self.alloc_expr_desugared_with_ptr( + Expr::Tuple { exprs: Box::new([]), is_assignee_expr: false }, + ptr, + ); + self.alloc_expr_desugared_with_ptr( + Expr::Call { callee, args: Box::new([unit]), is_assignee_expr: false }, + ptr, + ) } }; let Expr::Block { tail, .. } = &mut self.body.exprs[expr_id] else { @@ -1067,16 +1055,12 @@ impl ExprCollector<'_> { None => None, }, ); - match expansion { - Some(tail) => { - // Make the macro-call point to its expanded expression so we can query - // semantics on syntax pointers to the macro - let src = self.expander.in_file(syntax_ptr); - self.source_map.expr_map.insert(src, tail); - Some(tail) - } - None => None, - } + expansion.inspect(|&tail| { + // Make the macro-call point to its expanded expression so we can query + // semantics on syntax pointers to the macro + let src = self.expander.in_file(syntax_ptr); + self.source_map.expr_map.insert(src, tail); + }) } fn collect_stmt(&mut self, statements: &mut Vec, s: ast::Stmt) { @@ -1261,7 +1245,7 @@ impl ExprCollector<'_> { (Some(id), Pat::Bind { id, subpat }) }; - let ptr = AstPtr::new(&Either::Left(pat)); + let ptr = AstPtr::new(&pat); let pat = self.alloc_pat(pattern, ptr); if let Some(binding_id) = binding { self.add_definition_to_binding(binding_id, pat); @@ -1359,9 +1343,10 @@ impl ExprCollector<'_> { suffix: suffix.into_iter().map(|p| self.collect_pat(p, binding_list)).collect(), } } - #[rustfmt::skip] // https://github.com/rust-lang/rustfmt/issues/5676 ast::Pat::LiteralPat(lit) => 'b: { - let Some((hir_lit, ast_lit)) = pat_literal_to_hir(lit) else { break 'b Pat::Missing }; + let Some((hir_lit, ast_lit)) = pat_literal_to_hir(lit) else { + break 'b Pat::Missing; + }; let expr = Expr::Literal(hir_lit); let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit)); let expr_id = self.alloc_expr(expr, expr_ptr); @@ -1397,7 +1382,7 @@ impl ExprCollector<'_> { ast::Pat::MacroPat(mac) => match mac.macro_call() { Some(call) => { let macro_ptr = AstPtr::new(&call); - let src = self.expander.in_file(AstPtr::new(&Either::Left(pat))); + let src = self.expander.in_file(AstPtr::new(&pat)); let pat = self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { this.collect_pat_opt(expanded_pat, binding_list) @@ -1426,7 +1411,7 @@ impl ExprCollector<'_> { Pat::Range { start, end } } }; - let ptr = AstPtr::new(&Either::Left(pat)); + let ptr = AstPtr::new(&pat); self.alloc_pat(pattern, ptr) } @@ -1987,10 +1972,19 @@ impl ExprCollector<'_> { self.source_map.expr_map.insert(src, id); id } - // FIXME: desugared exprs don't have ptr, that's wrong and should be fixed somehow. + // FIXME: desugared exprs don't have ptr, that's wrong and should be fixed. + // Migrate to alloc_expr_desugared_with_ptr and then rename back fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { self.body.exprs.alloc(expr) } + fn alloc_expr_desugared_with_ptr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { + let src = self.expander.in_file(ptr); + let id = self.body.exprs.alloc(expr); + self.source_map.expr_map_back.insert(id, src); + // We intentionally don't fill this as it could overwrite a non-desugared entry + // self.source_map.expr_map.insert(src, id); + id + } fn missing_expr(&mut self) -> ExprId { self.alloc_expr_desugared(Expr::Missing) } diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index b2aab55a6a..cbb5ca887f 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -48,7 +48,16 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false }; if let DefWithBodyId::FunctionId(it) = owner { p.buf.push('('); - body.params.iter().zip(db.function_data(it).params.iter()).for_each(|(¶m, ty)| { + let params = &db.function_data(it).params; + let mut params = params.iter(); + if let Some(self_param) = body.self_param { + p.print_binding(self_param); + p.buf.push(':'); + if let Some(ty) = params.next() { + p.print_type_ref(ty); + } + } + body.params.iter().zip(params).for_each(|(¶m, ty)| { p.print_pat(param); p.buf.push(':'); p.print_type_ref(ty); diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index 69b82ae871..0020e4eac3 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -96,6 +96,9 @@ impl ExprScopes { scope_by_expr: ArenaMap::with_capacity(body.exprs.len()), }; let mut root = scopes.root_scope(); + if let Some(self_param) = body.self_param { + scopes.add_bindings(body, root, self_param); + } scopes.add_params_bindings(body, root, &body.params); compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root); scopes diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index f1c6b3b89f..0b41984bdd 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -76,7 +76,7 @@ impl ChildBySource for ItemScope { self.extern_crate_decls() .for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::EXTERN_CRATE)); self.use_decls().for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::USE)); - self.unnamed_consts(db) + self.unnamed_consts() .for_each(|konst| insert_item_loc(db, res, file_id, konst, keys::CONST)); self.attr_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each( |(ast_id, call_id)| { diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index d4c1db8b95..b815c9b73e 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -715,7 +715,7 @@ impl<'a> AssocItemCollector<'a> { } AssocItem::MacroCall(call) => { let file_id = self.expander.current_file_id(); - let MacroCall { ast_id, expand_to, call_site, ref path } = item_tree[call]; + let MacroCall { ast_id, expand_to, ctxt, ref path } = item_tree[call]; let module = self.expander.module.local_id; let resolver = |path| { @@ -734,7 +734,7 @@ impl<'a> AssocItemCollector<'a> { match macro_call_as_call_id( self.db.upcast(), &AstIdWithPath::new(file_id, ast_id, Clone::clone(path)), - call_site, + ctxt, expand_to, self.expander.module.krate(), resolver, @@ -745,6 +745,7 @@ impl<'a> AssocItemCollector<'a> { self.collect_macro_items(res, &|| hir_expand::MacroCallKind::FnLike { ast_id: InFile::new(file_id, ast_id), expand_to: hir_expand::ExpandTo::Items, + eager: None, }); } Ok(None) => (), @@ -754,6 +755,7 @@ impl<'a> AssocItemCollector<'a> { MacroCallKind::FnLike { ast_id: InFile::new(file_id, ast_id), expand_to, + eager: None, }, Clone::clone(path), )); diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs index 5790e600f6..a7461b78af 100644 --- a/crates/hir-def/src/data/adt.rs +++ b/crates/hir-def/src/data/adt.rs @@ -191,9 +191,9 @@ impl StructData { let krate = loc.container.krate; let item_tree = loc.id.item_tree(db); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); - let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); + let cfg_options = db.crate_graph()[krate].cfg_options.clone(); - let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); + let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()); let mut flags = StructFlags::NO_FLAGS; if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { @@ -248,9 +248,9 @@ impl StructData { let krate = loc.container.krate; let item_tree = loc.id.item_tree(db); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); - let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); + let cfg_options = db.crate_graph()[krate].cfg_options.clone(); - let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); + let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()); let mut flags = StructFlags::NO_FLAGS; if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL; diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 544ed6bc34..30d52d87f1 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -309,13 +309,9 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()), local_inner: false, allow_internal_unsafe: loc.allow_internal_unsafe, - span: db - .span_map(loc.id.file_id()) - .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), edition: loc.edition, } } - MacroId::MacroRulesId(it) => { let loc: MacroRulesLoc = it.lookup(db); @@ -328,9 +324,6 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { allow_internal_unsafe: loc .flags .contains(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE), - span: db - .span_map(loc.id.file_id()) - .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), edition: loc.edition, } } @@ -348,9 +341,6 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { ), local_inner: false, allow_internal_unsafe: false, - span: db - .span_map(loc.id.file_id()) - .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), edition: loc.edition, } } diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 0e6826a75a..2b059d1f8d 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -241,30 +241,8 @@ impl ItemScope { }) } - pub fn unnamed_consts<'a>( - &'a self, - db: &'a dyn DefDatabase, - ) -> impl Iterator + 'a { - // FIXME: Also treat consts named `_DERIVE_*` as unnamed, since synstructure generates those. - // Should be removed once synstructure stops doing that. - let synstructure_hack_consts = self.values.values().filter_map(|(item, _, _)| match item { - &ModuleDefId::ConstId(id) => { - let loc = id.lookup(db); - let item_tree = loc.id.item_tree(db); - if item_tree[loc.id.value] - .name - .as_ref() - .map_or(false, |n| n.to_smol_str().starts_with("_DERIVE_")) - { - Some(id) - } else { - None - } - } - _ => None, - }); - - self.unnamed_consts.iter().copied().chain(synstructure_hack_consts) + pub fn unnamed_consts(&self) -> impl Iterator + '_ { + self.unnamed_consts.iter().copied() } /// Iterate over all module scoped macros diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index bd3d377ec0..585e93ce21 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -49,7 +49,7 @@ use intern::Interned; use la_arena::{Arena, Idx, IdxRange, RawIdx}; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use span::{AstIdNode, FileAstId, Span}; +use span::{AstIdNode, FileAstId, SyntaxContextId}; use stdx::never; use syntax::{ast, match_ast, SyntaxKind}; use triomphe::Arc; @@ -790,8 +790,7 @@ pub struct MacroCall { pub path: Interned, pub ast_id: FileAstId, pub expand_to: ExpandTo, - // FIXME: We need to move this out. It invalidates the item tree when typing inside the macro call. - pub call_site: Span, + pub ctxt: SyntaxContextId, } #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index bf3d54f4ca..f02163cbe4 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -560,35 +560,32 @@ impl<'a> Ctx<'a> { fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option> { let span_map = self.span_map(); - let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, &mut |range| { + let path = m.path()?; + let range = path.syntax().text_range(); + let path = Interned::new(ModPath::from_src(self.db.upcast(), path, &mut |range| { span_map.span_for_range(range).ctx })?); let ast_id = self.source_ast_id_map.ast_id(m); let expand_to = hir_expand::ExpandTo::from_call_site(m); - let res = MacroCall { - path, - ast_id, - expand_to, - call_site: span_map.span_for_range(m.syntax().text_range()), - }; + let res = MacroCall { path, ast_id, expand_to, ctxt: span_map.span_for_range(range).ctx }; Some(id(self.data().macro_calls.alloc(res))) } fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option> { - let name = m.name().map(|it| it.as_name())?; + let name = m.name()?; let ast_id = self.source_ast_id_map.ast_id(m); - let res = MacroRules { name, ast_id }; + let res = MacroRules { name: name.as_name(), ast_id }; Some(id(self.data().macro_rules.alloc(res))) } fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option> { - let name = m.name().map(|it| it.as_name())?; + let name = m.name()?; let ast_id = self.source_ast_id_map.ast_id(m); let visibility = self.lower_visibility(m); - let res = Macro2 { name, ast_id, visibility }; + let res = Macro2 { name: name.as_name(), ast_id, visibility }; Some(id(self.data().macro_defs.alloc(res))) } diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 87c90a4c6a..953bf6b85d 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -487,12 +487,12 @@ impl Printer<'_> { } } ModItem::MacroCall(it) => { - let MacroCall { path, ast_id, expand_to, call_site } = &self.tree[it]; + let MacroCall { path, ast_id, expand_to, ctxt } = &self.tree[it]; let _ = writeln!( self, - "// AstId: {:?}, Span: {}, ExpandTo: {:?}", + "// AstId: {:?}, SyntaxContext: {}, ExpandTo: {:?}", ast_id.erase().into_raw(), - call_site, + ctxt, expand_to ); wln!(self, "{}!(...);", path.display(self.db.upcast())); diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 26f7b41c77..48da876ac1 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -278,7 +278,7 @@ m!(); // AstId: 2 pub macro m2 { ... } - // AstId: 3, Span: 0:3@0..5#0, ExpandTo: Items + // AstId: 3, SyntaxContext: 0, ExpandTo: Items m!(...); "#]], ); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index d63f2268aa..828842de7e 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -90,7 +90,7 @@ use hir_expand::{ use item_tree::ExternBlock; use la_arena::Idx; use nameres::DefMap; -use span::{AstIdNode, FileAstId, FileId, Span}; +use span::{AstIdNode, FileAstId, FileId, SyntaxContextId}; use stdx::impl_from; use syntax::{ast, AstNode}; @@ -1342,21 +1342,22 @@ impl AsMacroCall for InFile<&ast::MacroCall> { let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); let span_map = db.span_map(self.file_id); let path = self.value.path().and_then(|path| { - path::ModPath::from_src(db, path, &mut |range| { + let range = path.syntax().text_range(); + let mod_path = path::ModPath::from_src(db, path, &mut |range| { span_map.as_ref().span_for_range(range).ctx - }) + })?; + let call_site = span_map.span_for_range(range); + Some((call_site, mod_path)) }); - let Some(path) = path else { + let Some((call_site, path)) = path else { return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation"))); }; - let call_site = span_map.span_for_range(self.value.syntax().text_range()); - macro_call_as_call_id_with_eager( db, &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), - call_site, + call_site.ctx, expands_to, krate, resolver, @@ -1381,7 +1382,7 @@ impl AstIdWithPath { fn macro_call_as_call_id( db: &dyn ExpandDatabase, call: &AstIdWithPath, - call_site: Span, + call_site: SyntaxContextId, expand_to: ExpandTo, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option + Copy, @@ -1393,7 +1394,7 @@ fn macro_call_as_call_id( fn macro_call_as_call_id_with_eager( db: &dyn ExpandDatabase, call: &AstIdWithPath, - call_site: Span, + call_site: SyntaxContextId, expand_to: ExpandTo, krate: CrateId, resolver: impl FnOnce(path::ModPath) -> Option, @@ -1403,17 +1404,20 @@ fn macro_call_as_call_id_with_eager( resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?; let res = match def.kind { - MacroDefKind::BuiltInEager(..) => { - let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); - expand_eager_macro_input(db, krate, macro_call, def, call_site, &|path| { - eager_resolver(path).filter(MacroDefId::is_fn_like) - }) - } + MacroDefKind::BuiltInEager(..) => expand_eager_macro_input( + db, + krate, + &call.ast_id.to_node(db), + call.ast_id, + def, + call_site, + &|path| eager_resolver(path).filter(MacroDefId::is_fn_like), + ), _ if def.is_fn_like() => ExpandResult { - value: Some(def.as_lazy_macro( + value: Some(def.make_call( db, krate, - MacroCallKind::FnLike { ast_id: call.ast_id, expand_to }, + MacroCallKind::FnLike { ast_id: call.ast_id, expand_to, eager: None }, call_site, )), err: None, diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs index 86b4466153..89c1b44608 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -528,3 +528,121 @@ impl < > $crate::fmt::Debug for Command< > where { }"#]], ); } +#[test] +fn test_debug_expand_with_cfg() { + check( + r#" + //- minicore: derive, fmt + use core::fmt::Debug; + + #[derive(Debug)] + struct HideAndShow { + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } + #[derive(Debug)] + enum HideAndShowEnum { + #[cfg(never)] + AlwaysHide, + #[cfg(not(never))] + AlwaysShow{ + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } + } + "#, + expect![[r#" +use core::fmt::Debug; + +#[derive(Debug)] +struct HideAndShow { + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, +} +#[derive(Debug)] +enum HideAndShowEnum { + #[cfg(never)] + AlwaysHide, + #[cfg(not(never))] + AlwaysShow{ + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } +} + +impl < > $crate::fmt::Debug for HideAndShow< > where { + fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { + match self { + HideAndShow { + always_show: always_show, + } + =>f.debug_struct("HideAndShow").field("always_show", &always_show).finish() + } + } +} +impl < > $crate::fmt::Debug for HideAndShowEnum< > where { + fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { + match self { + HideAndShowEnum::AlwaysShow { + always_show: always_show, + } + =>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(), + } + } +}"#]], + ); +} +#[test] +fn test_default_expand_with_cfg() { + check( + r#" +//- minicore: derive, default +#[derive(Default)] +struct Foo { + field1: i32, + #[cfg(never)] + field2: (), +} +#[derive(Default)] +enum Bar { + Foo, + #[cfg_attr(not(never), default)] + Bar, +} +"#, + expect![[r#" +#[derive(Default)] +struct Foo { + field1: i32, + #[cfg(never)] + field2: (), +} +#[derive(Default)] +enum Bar { + Foo, + #[cfg_attr(not(never), default)] + Bar, +} + +impl < > $crate::default::Default for Foo< > where { + fn default() -> Self { + Foo { + field1: $crate::default::Default::default(), + } + } +} +impl < > $crate::default::Default for Bar< > where { + fn default() -> Self { + Bar::Bar + } +}"#]], + ); +} diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index edc8247f16..965f329acb 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -171,7 +171,7 @@ fn main(foo: ()) { } fn main(foo: ()) { - /* error: unresolved macro unresolved */"helloworld!"#0:3@207..323#2#; + /* error: unresolved macro unresolved */"helloworld!"#0:3@236..321#0#; } } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs index 63f211022c..23d8b023b8 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -33,7 +33,7 @@ m!(&k"); "#, expect![[r#" macro_rules! m { ($i:literal) => {}; } -/* error: mismatched delimiters */"#]], +/* error: expected literal */"#]], ); } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs b/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs index 362c189f6a..fb5797d6e5 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs @@ -98,7 +98,7 @@ macro_rules! m1 { ($x:ident) => { ($x } } macro_rules! m2 { ($x:ident) => {} } /* error: macro definition has parse errors */ -/* error: mismatched delimiters */ +/* error: expected ident */ "#]], ) } diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 764617eafb..b56dee3efb 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -61,15 +61,16 @@ use std::ops::Deref; use base_db::{CrateId, Edition, FileId}; use hir_expand::{ - name::Name, proc_macro::ProcMacroKind, HirFileId, InFile, MacroCallId, MacroDefId, + name::Name, proc_macro::ProcMacroKind, ErasedAstId, HirFileId, InFile, MacroCallId, MacroDefId, }; use itertools::Itertools; use la_arena::Arena; use rustc_hash::{FxHashMap, FxHashSet}; -use span::FileAstId; +use span::{FileAstId, ROOT_ERASED_FILE_AST_ID}; use stdx::format_to; use syntax::{ast, SmolStr}; use triomphe::Arc; +use tt::TextRange; use crate::{ db::DefDatabase, @@ -677,6 +678,25 @@ impl ModuleData { } } + pub fn definition_source_range(&self, db: &dyn DefDatabase) -> InFile { + match &self.origin { + &ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => { + InFile::new( + definition.into(), + ErasedAstId::new(definition.into(), ROOT_ERASED_FILE_AST_ID) + .to_range(db.upcast()), + ) + } + &ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new( + definition_tree_id.file_id(), + AstId::new(definition_tree_id.file_id(), definition).to_range(db.upcast()), + ), + ModuleOrigin::BlockExpr { block, .. } => { + InFile::new(block.file_id, block.to_range(db.upcast())) + } + } + } + /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. /// `None` for the crate root or block. pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option> { @@ -684,6 +704,13 @@ impl ModuleData { let value = decl.to_node(db.upcast()); Some(InFile { file_id: decl.file_id, value }) } + + /// Returns the range which declares this module, either a `mod foo;` or a `mod foo {}`. + /// `None` for the crate root or block. + pub fn declaration_source_range(&self, db: &dyn DefDatabase) -> Option> { + let decl = self.origin.declaration()?; + Some(InFile { file_id: decl.file_id, value: decl.to_range(db.upcast()) }) + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/hir-def/src/nameres/attr_resolution.rs b/crates/hir-def/src/nameres/attr_resolution.rs index 1cadae8c87..662c80edf3 100644 --- a/crates/hir-def/src/nameres/attr_resolution.rs +++ b/crates/hir-def/src/nameres/attr_resolution.rs @@ -5,7 +5,7 @@ use hir_expand::{ attrs::{Attr, AttrId, AttrInput}, MacroCallId, MacroCallKind, MacroDefId, }; -use span::Span; +use span::SyntaxContextId; use syntax::{ast, SmolStr}; use triomphe::Arc; @@ -109,14 +109,14 @@ pub(super) fn attr_macro_as_call_id( let arg = match macro_attr.input.as_deref() { Some(AttrInput::TokenTree(tt)) => { let mut tt = tt.as_ref().clone(); - tt.delimiter = tt::Delimiter::invisible_spanned(macro_attr.span); + tt.delimiter.kind = tt::DelimiterKind::Invisible; Some(tt) } _ => None, }; - def.as_lazy_macro( + def.make_call( db.upcast(), krate, MacroCallKind::Attr { @@ -124,7 +124,7 @@ pub(super) fn attr_macro_as_call_id( attr_args: arg.map(Arc::new), invoc_attr_index: macro_attr.id, }, - macro_attr.span, + macro_attr.ctxt, ) } @@ -133,14 +133,14 @@ pub(super) fn derive_macro_as_call_id( item_attr: &AstIdWithPath, derive_attr_index: AttrId, derive_pos: u32, - call_site: Span, + call_site: SyntaxContextId, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>, ) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> { let (macro_id, def_id) = resolver(item_attr.path.clone()) .filter(|(_, def_id)| def_id.is_derive()) .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?; - let call_id = def_id.as_lazy_macro( + let call_id = def_id.make_call( db.upcast(), krate, MacroCallKind::Derive { diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index f9fe6d3b90..3d026447fb 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -230,13 +230,13 @@ enum MacroDirectiveKind { FnLike { ast_id: AstIdWithPath, expand_to: ExpandTo, - call_site: Span, + ctxt: SyntaxContextId, }, Derive { ast_id: AstIdWithPath, derive_attr: AttrId, derive_pos: usize, - call_site: Span, + ctxt: SyntaxContextId, }, Attr { ast_id: AstIdWithPath, @@ -1126,7 +1126,7 @@ impl DefCollector<'_> { let resolver_def_id = |path| resolver(path).map(|(_, it)| it); match &directive.kind { - MacroDirectiveKind::FnLike { ast_id, expand_to, call_site } => { + MacroDirectiveKind::FnLike { ast_id, expand_to, ctxt: call_site } => { let call_id = macro_call_as_call_id( self.db.upcast(), ast_id, @@ -1146,7 +1146,7 @@ impl DefCollector<'_> { return Resolved::Yes; } } - MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos, call_site } => { + MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos, ctxt: call_site } => { let id = derive_macro_as_call_id( self.db, ast_id, @@ -1266,7 +1266,7 @@ impl DefCollector<'_> { ast_id, derive_attr: attr.id, derive_pos: idx, - call_site, + ctxt: call_site.ctx, }, container: directive.container, }); @@ -1428,7 +1428,7 @@ impl DefCollector<'_> { for directive in &self.unresolved_macros { match &directive.kind { - MacroDirectiveKind::FnLike { ast_id, expand_to, call_site } => { + MacroDirectiveKind::FnLike { ast_id, expand_to, ctxt: call_site } => { // FIXME: we shouldn't need to re-resolve the macro here just to get the unresolved error! let macro_call_as_call_id = macro_call_as_call_id( self.db.upcast(), @@ -1451,12 +1451,16 @@ impl DefCollector<'_> { if let Err(UnresolvedMacro { path }) = macro_call_as_call_id { self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( directive.module_id, - MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: *expand_to }, + MacroCallKind::FnLike { + ast_id: ast_id.ast_id, + expand_to: *expand_to, + eager: None, + }, path, )); } } - MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos, call_site: _ } => { + MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos, ctxt: _ } => { self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( directive.module_id, MacroCallKind::Derive { @@ -2285,7 +2289,7 @@ impl ModCollector<'_, '_> { fn collect_macro_call( &mut self, - &MacroCall { ref path, ast_id, expand_to, call_site }: &MacroCall, + &MacroCall { ref path, ast_id, expand_to, ctxt }: &MacroCall, container: ItemContainerId, ) { let ast_id = AstIdWithPath::new(self.file_id(), ast_id, ModPath::clone(path)); @@ -2299,7 +2303,7 @@ impl ModCollector<'_, '_> { if let Ok(res) = macro_call_as_call_id_with_eager( db.upcast(), &ast_id, - call_site, + ctxt, expand_to, self.def_collector.def_map.krate, |path| { @@ -2357,7 +2361,7 @@ impl ModCollector<'_, '_> { self.def_collector.unresolved_macros.push(MacroDirective { module_id: self.module_id, depth: self.macro_depth + 1, - kind: MacroDirectiveKind::FnLike { ast_id, expand_to, call_site }, + kind: MacroDirectiveKind::FnLike { ast_id, expand_to, ctxt }, container, }); } diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index 6efced0271..be41634eb5 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -1,6 +1,5 @@ -use base_db::{SourceDatabase, SourceDatabaseExt}; +use base_db::{SourceDatabase, SourceDatabaseExt2 as _}; use test_fixture::WithFixture; -use triomphe::Arc; use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId}; @@ -17,7 +16,7 @@ fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: }); assert!(format!("{events:?}").contains("crate_def_map"), "{events:#?}") } - db.set_file_text(pos.file_id, Arc::from(ra_fixture_change)); + db.set_file_text(pos.file_id, ra_fixture_change); { let events = db.log_executed(|| { @@ -267,7 +266,7 @@ fn quux() { 92 } m!(Y); m!(Z); "#; - db.set_file_text(pos.file_id, Arc::from(new_text)); + db.set_file_text(pos.file_id, new_text); { let events = db.log_executed(|| { diff --git a/crates/hir-def/src/src.rs b/crates/hir-def/src/src.rs index 4283f003f8..2b1da8c34e 100644 --- a/crates/hir-def/src/src.rs +++ b/crates/hir-def/src/src.rs @@ -3,7 +3,7 @@ use either::Either; use hir_expand::InFile; use la_arena::ArenaMap; -use syntax::ast; +use syntax::{ast, AstNode, AstPtr}; use crate::{ data::adt::lower_struct, db::DefDatabase, item_tree::ItemTreeNode, trace::Trace, GenericDefId, @@ -12,8 +12,12 @@ use crate::{ }; pub trait HasSource { - type Value; - fn source(&self, db: &dyn DefDatabase) -> InFile; + type Value: AstNode; + fn source(&self, db: &dyn DefDatabase) -> InFile { + let InFile { file_id, value } = self.ast_ptr(db); + InFile::new(file_id, value.to_node(&db.parse_or_expand(file_id))) + } + fn ast_ptr(&self, db: &dyn DefDatabase) -> InFile>; } impl HasSource for T @@ -22,16 +26,14 @@ where T::Id: ItemTreeNode, { type Value = ::Source; - - fn source(&self, db: &dyn DefDatabase) -> InFile { + fn ast_ptr(&self, db: &dyn DefDatabase) -> InFile> { let id = self.item_tree_id(); let file_id = id.file_id(); let tree = id.item_tree(db); let ast_id_map = db.ast_id_map(file_id); - let root = db.parse_or_expand(file_id); let node = &tree[id.value]; - InFile::new(file_id, ast_id_map.get(node.ast_id()).to_node(&root)) + InFile::new(file_id, ast_id_map.get(node.ast_id())) } } diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs index 7793e99532..af3ecdcd5e 100644 --- a/crates/hir-expand/src/attrs.rs +++ b/crates/hir-expand/src/attrs.rs @@ -7,7 +7,7 @@ use either::Either; use intern::Interned; use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; use smallvec::{smallvec, SmallVec}; -use span::Span; +use span::{Span, SyntaxContextId}; use syntax::{ast, match_ast, AstNode, AstToken, SmolStr, SyntaxNode}; use triomphe::Arc; @@ -53,7 +53,7 @@ impl RawAttrs { id, input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), path: Interned::new(ModPath::from(crate::name!(doc))), - span: span_map.span_for_range(comment.syntax().text_range()), + ctxt: span_map.span_for_range(comment.syntax().text_range()).ctx, }), }); let entries: Arc<[Attr]> = Arc::from_iter(entries); @@ -173,7 +173,7 @@ pub struct Attr { pub id: AttrId, pub path: Interned, pub input: Option>, - pub span: Span, + pub ctxt: SyntaxContextId, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -201,10 +201,12 @@ impl Attr { span_map: SpanMapRef<'_>, id: AttrId, ) -> Option { - let path = Interned::new(ModPath::from_src(db, ast.path()?, &mut |range| { + let path = ast.path()?; + let range = path.syntax().text_range(); + let path = Interned::new(ModPath::from_src(db, path, &mut |range| { span_map.span_for_range(range).ctx })?); - let span = span_map.span_for_range(ast.syntax().text_range()); + let span = span_map.span_for_range(range); let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { let value = match lit.kind() { ast::LiteralKind::String(string) => string.value()?.into(), @@ -217,11 +219,11 @@ impl Attr { } else { None }; - Some(Attr { id, path, input, span }) + Some(Attr { id, path, input, ctxt: span.ctx }) } fn from_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree], id: AttrId) -> Option { - let span = tt.first()?.first_span(); + let ctxt = tt.first()?.first_span().ctx; let path_end = tt .iter() .position(|tt| { @@ -253,7 +255,7 @@ impl Attr { } _ => None, }; - Some(Attr { id, path, input, span }) + Some(Attr { id, path, input, ctxt }) } pub fn path(&self) -> &ModPath { diff --git a/crates/hir-expand/src/builtin_attr_macro.rs b/crates/hir-expand/src/builtin_attr_macro.rs index a0102f36af..9ff29b484d 100644 --- a/crates/hir-expand/src/builtin_attr_macro.rs +++ b/crates/hir-expand/src/builtin_attr_macro.rs @@ -11,7 +11,7 @@ macro_rules! register_builtin { } impl BuiltinAttrExpander { - pub fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree) -> ExpandResult { + pub fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { match *self { $( BuiltinAttrExpander::$variant => $expand, )* } @@ -34,8 +34,9 @@ impl BuiltinAttrExpander { db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, + span: Span, ) -> ExpandResult { - self.expander()(db, id, tt) + self.expander()(db, id, tt, span) } pub fn is_derive(self) -> bool { @@ -71,6 +72,7 @@ fn dummy_attr_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, + _span: Span, ) -> ExpandResult { ExpandResult::ok(tt.clone()) } @@ -100,6 +102,7 @@ fn derive_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, + span: Span, ) -> ExpandResult { let loc = db.lookup_intern_macro_call(id); let derives = match &loc.kind { @@ -107,17 +110,14 @@ fn derive_expand( attr_args } _ => { - return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan { - open: loc.call_site, - close: loc.call_site, - })) + return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan { open: span, close: span })) } }; - pseudo_derive_attr_expansion(tt, derives, loc.call_site) + pseudo_derive_attr_expansion(tt, derives, span) } pub fn pseudo_derive_attr_expansion( - tt: &tt::Subtree, + _: &tt::Subtree, args: &tt::Subtree, call_site: Span, ) -> ExpandResult { @@ -141,7 +141,7 @@ pub fn pseudo_derive_attr_expansion( token_trees.push(mk_leaf(']')); } ExpandResult::ok(tt::Subtree { - delimiter: tt.delimiter, + delimiter: args.delimiter, token_trees: token_trees.into_boxed_slice(), }) } diff --git a/crates/hir-expand/src/builtin_derive_macro.rs b/crates/hir-expand/src/builtin_derive_macro.rs index 66dec7d89e..528038a9cc 100644 --- a/crates/hir-expand/src/builtin_derive_macro.rs +++ b/crates/hir-expand/src/builtin_derive_macro.rs @@ -50,8 +50,8 @@ impl BuiltinDeriveExpander { db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, + span: Span, ) -> ExpandResult { - let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); self.expander()(span, tt) } diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index 0fd0c25dcc..9fb6a0b234 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -19,14 +19,14 @@ use crate::{ }; macro_rules! register_builtin { - ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { + ( $LAZY:ident: $(($name:ident, $kind: ident) => $expand:ident),* , $EAGER:ident: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinFnLikeExpander { + pub enum $LAZY { $($kind),* } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum EagerExpander { + pub enum $EAGER { $($e_kind),* } @@ -62,8 +62,8 @@ impl BuiltinFnLikeExpander { db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, + span: Span, ) -> ExpandResult { - let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); self.expander()(db, id, tt, span) } @@ -75,8 +75,8 @@ impl EagerExpander { db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, + span: Span, ) -> ExpandResult { - let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); self.expander()(db, id, tt, span) } @@ -84,6 +84,17 @@ impl EagerExpander { pub fn is_include(&self) -> bool { matches!(self, EagerExpander::Include) } + + pub fn is_include_like(&self) -> bool { + matches!( + self, + EagerExpander::Include | EagerExpander::IncludeStr | EagerExpander::IncludeBytes + ) + } + + pub fn is_env_or_option_env(&self) -> bool { + matches!(self, EagerExpander::Env | EagerExpander::OptionEnv) + } } pub fn find_builtin_macro( @@ -93,7 +104,7 @@ pub fn find_builtin_macro( } register_builtin! { - LAZY: + BuiltinFnLikeExpander: (column, Column) => line_expand, (file, File) => file_expand, (line, Line) => line_expand, @@ -114,7 +125,7 @@ register_builtin! { (format_args_nl, FormatArgsNl) => format_args_nl_expand, (quote, Quote) => quote_expand, - EAGER: + EagerExpander: (compile_error, CompileError) => compile_error_expand, (concat, Concat) => concat_expand, (concat_idents, ConcatIdents) => concat_idents_expand, @@ -426,22 +437,25 @@ fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool { } } -fn unquote_str(lit: &tt::Literal) -> Option { +fn unquote_str(lit: &tt::Literal) -> Option<(String, Span)> { + let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::String::cast(lit)?; - token.value().map(|it| it.into_owned()) + token.value().map(|it| (it.into_owned(), span)) } -fn unquote_char(lit: &tt::Literal) -> Option { +fn unquote_char(lit: &tt::Literal) -> Option<(char, Span)> { + let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::Char::cast(lit)?; - token.value() + token.value().zip(Some(span)) } -fn unquote_byte_string(lit: &tt::Literal) -> Option> { +fn unquote_byte_string(lit: &tt::Literal) -> Option<(Vec, Span)> { + let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::ByteString::cast(lit)?; - token.value().map(|it| it.into_owned()) + token.value().map(|it| (it.into_owned(), span)) } fn compile_error_expand( @@ -452,7 +466,7 @@ fn compile_error_expand( ) -> ExpandResult { let err = match &*tt.token_trees { [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { - Some(unquoted) => ExpandError::other(unquoted.into_boxed_str()), + Some((unquoted, _)) => ExpandError::other(unquoted.into_boxed_str()), None => ExpandError::other("`compile_error!` argument must be a string"), }, _ => ExpandError::other("`compile_error!` argument must be a string"), @@ -465,10 +479,16 @@ fn concat_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, - span: Span, + _: Span, ) -> ExpandResult { let mut err = None; let mut text = String::new(); + let mut span: Option = None; + let mut record_span = |s: Span| match &mut span { + Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), + Some(_) => (), + None => span = Some(s), + }; for (i, mut t) in tt.token_trees.iter().enumerate() { // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses // to ensure the right parsing order, so skip the parentheses here. Ideally we'd @@ -486,11 +506,14 @@ fn concat_expand( // concat works with string and char literals, so remove any quotes. // It also works with integer, float and boolean literals, so just use the rest // as-is. - if let Some(c) = unquote_char(it) { + if let Some((c, span)) = unquote_char(it) { text.push(c); + record_span(span); } else { - let component = unquote_str(it).unwrap_or_else(|| it.text.to_string()); + let (component, span) = + unquote_str(it).unwrap_or_else(|| (it.text.to_string(), it.span)); text.push_str(&component); + record_span(span); } } // handle boolean literals @@ -498,6 +521,7 @@ fn concat_expand( if i % 2 == 0 && (id.text == "true" || id.text == "false") => { text.push_str(id.text.as_str()); + record_span(id.span); } tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), _ => { @@ -505,6 +529,7 @@ fn concat_expand( } } } + let span = span.unwrap_or(tt.delimiter.open); ExpandResult { value: quote!(span =>#text), err } } @@ -512,18 +537,25 @@ fn concat_bytes_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, - span: Span, + call_site: Span, ) -> ExpandResult { let mut bytes = Vec::new(); let mut err = None; + let mut span: Option = None; + let mut record_span = |s: Span| match &mut span { + Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), + Some(_) => (), + None => span = Some(s), + }; for (i, t) in tt.token_trees.iter().enumerate() { match t { tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { let token = ast::make::tokens::literal(&lit.to_string()); + record_span(lit.span); match token.kind() { syntax::SyntaxKind::BYTE => bytes.push(token.text().to_owned()), syntax::SyntaxKind::BYTE_STRING => { - let components = unquote_byte_string(lit).unwrap_or_default(); + let components = unquote_byte_string(lit).map_or(vec![], |(it, _)| it); components.into_iter().for_each(|it| bytes.push(it.to_string())); } _ => { @@ -534,7 +566,7 @@ fn concat_bytes_expand( } tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => { - if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes) { + if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes, &mut record_span) { err.get_or_insert(e); break; } @@ -546,17 +578,24 @@ fn concat_bytes_expand( } } let value = tt::Subtree { - delimiter: tt::Delimiter { open: span, close: span, kind: tt::DelimiterKind::Bracket }, + delimiter: tt::Delimiter { + open: call_site, + close: call_site, + kind: tt::DelimiterKind::Bracket, + }, token_trees: { Itertools::intersperse_with( bytes.into_iter().map(|it| { - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { text: it.into(), span })) + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + text: it.into(), + span: span.unwrap_or(call_site), + })) }), || { tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone, - span, + span: call_site, })) }, ) @@ -569,13 +608,15 @@ fn concat_bytes_expand( fn concat_bytes_expand_subtree( tree: &tt::Subtree, bytes: &mut Vec, + mut record_span: impl FnMut(Span), ) -> Result<(), ExpandError> { for (ti, tt) in tree.token_trees.iter().enumerate() { match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { - let lit = ast::make::tokens::literal(&lit.to_string()); + tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => { + let lit = ast::make::tokens::literal(&it.to_string()); match lit.kind() { syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => { + record_span(it.span); bytes.push(lit.text().to_owned()) } _ => { @@ -635,7 +676,7 @@ fn relative_file( } } -fn parse_string(tt: &tt::Subtree) -> Result { +fn parse_string(tt: &tt::Subtree) -> Result<(String, Span), ExpandError> { tt.token_trees .first() .and_then(|tt| match tt { @@ -675,7 +716,7 @@ pub fn include_input_to_file_id( arg_id: MacroCallId, arg: &tt::Subtree, ) -> Result { - relative_file(db, arg_id, &parse_string(arg)?, false) + relative_file(db, arg_id, &parse_string(arg)?.0, false) } fn include_bytes_expand( @@ -701,7 +742,7 @@ fn include_str_expand( tt: &tt::Subtree, span: Span, ) -> ExpandResult { - let path = match parse_string(tt) { + let (path, span) = match parse_string(tt) { Ok(it) => it, Err(e) => { return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) @@ -736,7 +777,7 @@ fn env_expand( tt: &tt::Subtree, span: Span, ) -> ExpandResult { - let key = match parse_string(tt) { + let (key, span) = match parse_string(tt) { Ok(it) => it, Err(e) => { return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) @@ -766,18 +807,24 @@ fn option_env_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, - span: Span, + call_site: Span, ) -> ExpandResult { - let key = match parse_string(tt) { + let (key, span) = match parse_string(tt) { Ok(it) => it, Err(e) => { - return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) + return ExpandResult::new( + tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }), + e, + ) } }; - let dollar_crate = dollar_crate(span); + let dollar_crate = dollar_crate(call_site); let expanded = match get_env_inner(db, arg_id, &key) { - None => quote! {span => #dollar_crate::option::Option::None::<&str> }, - Some(s) => quote! {span => #dollar_crate::option::Option::Some(#s) }, + None => quote! {call_site => #dollar_crate::option::Option::None::<&str> }, + Some(s) => { + let s = quote! (span => #s); + quote! {call_site => #dollar_crate::option::Option::Some(#s) } + } }; ExpandResult::ok(expanded) diff --git a/crates/hir-expand/src/cfg_process.rs b/crates/hir-expand/src/cfg_process.rs new file mode 100644 index 0000000000..c74c13a6fd --- /dev/null +++ b/crates/hir-expand/src/cfg_process.rs @@ -0,0 +1,327 @@ +//! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro +use std::iter::Peekable; + +use cfg::{CfgAtom, CfgExpr}; +use rustc_hash::FxHashSet; +use syntax::{ + ast::{self, Attr, HasAttrs, Meta, VariantList}, + AstNode, NodeOrToken, SyntaxElement, SyntaxNode, T, +}; +use tracing::{debug, warn}; +use tt::SmolStr; + +use crate::{db::ExpandDatabase, MacroCallKind, MacroCallLoc}; + +fn check_cfg_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option { + if !attr.simple_name().as_deref().map(|v| v == "cfg")? { + return None; + } + debug!("Evaluating cfg {}", attr); + let cfg = parse_from_attr_meta(attr.meta()?)?; + debug!("Checking cfg {:?}", cfg); + let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg) != Some(false); + Some(enabled) +} + +fn check_cfg_attr_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option { + if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? { + return None; + } + debug!("Evaluating cfg_attr {}", attr); + let cfg_expr = parse_from_attr_meta(attr.meta()?)?; + debug!("Checking cfg_attr {:?}", cfg_expr); + let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg_expr) != Some(false); + Some(enabled) +} + +fn process_has_attrs_with_possible_comma( + items: impl Iterator, + loc: &MacroCallLoc, + db: &dyn ExpandDatabase, + remove: &mut FxHashSet, +) -> Option<()> { + for item in items { + let field_attrs = item.attrs(); + 'attrs: for attr in field_attrs { + if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() { + debug!("censoring type {:?}", item.syntax()); + remove.insert(item.syntax().clone().into()); + // We need to remove the , as well + remove_possible_comma(&item, remove); + break 'attrs; + } + + if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) { + if enabled { + debug!("Removing cfg_attr tokens {:?}", attr); + let meta = attr.meta()?; + let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; + remove.extend(removes_from_cfg_attr); + } else { + debug!("censoring type cfg_attr {:?}", item.syntax()); + remove.insert(attr.syntax().clone().into()); + continue; + } + } + } + } + Some(()) +} +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum CfgExprStage { + /// Stripping the CFGExpr part of the attribute + StrippigCfgExpr, + /// Found the comma after the CFGExpr. Will keep all tokens until the next comma or the end of the attribute + FoundComma, + /// Everything following the attribute. This could be another attribute or the end of the attribute. + // FIXME: cfg_attr with multiple attributes will not be handled correctly. We will only keep the first attribute + // Related Issue: https://github.com/rust-lang/rust-analyzer/issues/10110 + EverythingElse, +} +/// This function creates its own set of tokens to remove. To help prevent malformed syntax as input. +fn remove_tokens_within_cfg_attr(meta: Meta) -> Option> { + let mut remove: FxHashSet = FxHashSet::default(); + debug!("Enabling attribute {}", meta); + let meta_path = meta.path()?; + debug!("Removing {:?}", meta_path.syntax()); + remove.insert(meta_path.syntax().clone().into()); + + let meta_tt = meta.token_tree()?; + debug!("meta_tt {}", meta_tt); + let mut stage = CfgExprStage::StrippigCfgExpr; + for tt in meta_tt.token_trees_and_tokens() { + debug!("Checking {:?}. Stage: {:?}", tt, stage); + match (stage, tt) { + (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Node(node)) => { + remove.insert(node.syntax().clone().into()); + } + (CfgExprStage::StrippigCfgExpr, syntax::NodeOrToken::Token(token)) => { + if token.kind() == T![,] { + stage = CfgExprStage::FoundComma; + } + remove.insert(token.into()); + } + (CfgExprStage::FoundComma, syntax::NodeOrToken::Token(token)) + if (token.kind() == T![,] || token.kind() == T![')']) => + { + // The end of the attribute or separator for the next attribute + stage = CfgExprStage::EverythingElse; + remove.insert(token.into()); + } + (CfgExprStage::EverythingElse, syntax::NodeOrToken::Node(node)) => { + remove.insert(node.syntax().clone().into()); + } + (CfgExprStage::EverythingElse, syntax::NodeOrToken::Token(token)) => { + remove.insert(token.into()); + } + // This is an actual attribute + _ => {} + } + } + if stage != CfgExprStage::EverythingElse { + warn!("Invalid cfg_attr attribute. {:?}", meta_tt); + return None; + } + Some(remove) +} +/// Removes a possible comma after the [AstNode] +fn remove_possible_comma(item: &impl AstNode, res: &mut FxHashSet) { + if let Some(comma) = item.syntax().next_sibling_or_token().filter(|it| it.kind() == T![,]) { + res.insert(comma); + } +} +fn process_enum( + variants: VariantList, + loc: &MacroCallLoc, + db: &dyn ExpandDatabase, + remove: &mut FxHashSet, +) -> Option<()> { + 'variant: for variant in variants.variants() { + for attr in variant.attrs() { + if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() { + // Rustc does not strip the attribute if it is enabled. So we will will leave it + debug!("censoring type {:?}", variant.syntax()); + remove.insert(variant.syntax().clone().into()); + // We need to remove the , as well + remove_possible_comma(&variant, remove); + continue 'variant; + }; + + if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) { + if enabled { + debug!("Removing cfg_attr tokens {:?}", attr); + let meta = attr.meta()?; + let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; + remove.extend(removes_from_cfg_attr); + } else { + debug!("censoring type cfg_attr {:?}", variant.syntax()); + remove.insert(attr.syntax().clone().into()); + continue; + } + } + } + if let Some(fields) = variant.field_list() { + match fields { + ast::FieldList::RecordFieldList(fields) => { + process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?; + } + ast::FieldList::TupleFieldList(fields) => { + process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?; + } + } + } + } + Some(()) +} + +pub(crate) fn process_cfg_attrs( + node: &SyntaxNode, + loc: &MacroCallLoc, + db: &dyn ExpandDatabase, +) -> Option> { + // FIXME: #[cfg_eval] is not implemented. But it is not stable yet + if !matches!(loc.kind, MacroCallKind::Derive { .. }) { + return None; + } + let mut remove = FxHashSet::default(); + + let item = ast::Item::cast(node.clone())?; + for attr in item.attrs() { + if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) { + if enabled { + debug!("Removing cfg_attr tokens {:?}", attr); + let meta = attr.meta()?; + let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?; + remove.extend(removes_from_cfg_attr); + } else { + debug!("censoring type cfg_attr {:?}", item.syntax()); + remove.insert(attr.syntax().clone().into()); + continue; + } + } + } + match item { + ast::Item::Struct(it) => match it.field_list()? { + ast::FieldList::RecordFieldList(fields) => { + process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?; + } + ast::FieldList::TupleFieldList(fields) => { + process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?; + } + }, + ast::Item::Enum(it) => { + process_enum(it.variant_list()?, loc, db, &mut remove)?; + } + ast::Item::Union(it) => { + process_has_attrs_with_possible_comma( + it.record_field_list()?.fields(), + loc, + db, + &mut remove, + )?; + } + // FIXME: Implement for other items if necessary. As we do not support #[cfg_eval] yet, we do not need to implement it for now + _ => {} + } + Some(remove) +} +/// Parses a `cfg` attribute from the meta +fn parse_from_attr_meta(meta: Meta) -> Option { + let tt = meta.token_tree()?; + let mut iter = tt.token_trees_and_tokens().skip(1).peekable(); + next_cfg_expr_from_syntax(&mut iter) +} + +fn next_cfg_expr_from_syntax(iter: &mut Peekable) -> Option +where + I: Iterator>, +{ + let name = match iter.next() { + None => return None, + Some(NodeOrToken::Token(element)) => match element.kind() { + syntax::T![ident] => SmolStr::new(element.text()), + _ => return Some(CfgExpr::Invalid), + }, + Some(_) => return Some(CfgExpr::Invalid), + }; + let result = match name.as_str() { + "all" | "any" | "not" => { + let mut preds = Vec::new(); + let Some(NodeOrToken::Node(tree)) = iter.next() else { + return Some(CfgExpr::Invalid); + }; + let mut tree_iter = tree.token_trees_and_tokens().skip(1).peekable(); + while tree_iter + .peek() + .filter( + |element| matches!(element, NodeOrToken::Token(token) if (token.kind() != syntax::T![')'])), + ) + .is_some() + { + let pred = next_cfg_expr_from_syntax(&mut tree_iter); + if let Some(pred) = pred { + preds.push(pred); + } + } + let group = match name.as_str() { + "all" => CfgExpr::All(preds), + "any" => CfgExpr::Any(preds), + "not" => CfgExpr::Not(Box::new(preds.pop().unwrap_or(CfgExpr::Invalid))), + _ => unreachable!(), + }; + Some(group) + } + _ => match iter.peek() { + Some(NodeOrToken::Token(element)) if (element.kind() == syntax::T![=]) => { + iter.next(); + match iter.next() { + Some(NodeOrToken::Token(value_token)) + if (value_token.kind() == syntax::SyntaxKind::STRING) => + { + let value = value_token.text(); + let value = SmolStr::new(value.trim_matches('"')); + Some(CfgExpr::Atom(CfgAtom::KeyValue { key: name, value })) + } + _ => None, + } + } + _ => Some(CfgExpr::Atom(CfgAtom::Flag(name))), + }, + }; + if let Some(NodeOrToken::Token(element)) = iter.peek() { + if element.kind() == syntax::T![,] { + iter.next(); + } + } + result +} +#[cfg(test)] +mod tests { + use cfg::DnfExpr; + use expect_test::{expect, Expect}; + use syntax::{ast::Attr, AstNode, SourceFile}; + + use crate::cfg_process::parse_from_attr_meta; + + fn check_dnf_from_syntax(input: &str, expect: Expect) { + let parse = SourceFile::parse(input); + let node = match parse.tree().syntax().descendants().find_map(Attr::cast) { + Some(it) => it, + None => { + let node = std::any::type_name::(); + panic!("Failed to make ast node `{node}` from text {input}") + } + }; + let node = node.clone_subtree(); + assert_eq!(node.syntax().text_range().start(), 0.into()); + + let cfg = parse_from_attr_meta(node.meta().unwrap()).unwrap(); + let actual = format!("#![cfg({})]", DnfExpr::new(cfg)); + expect.assert_eq(&actual); + } + #[test] + fn cfg_from_attr() { + check_dnf_from_syntax(r#"#[cfg(test)]"#, expect![[r#"#![cfg(test)]"#]]); + check_dnf_from_syntax(r#"#[cfg(not(never))]"#, expect![[r#"#![cfg(not(never))]"#]]); + } +} diff --git a/crates/hir-expand/src/change.rs b/crates/hir-expand/src/change.rs index 8b9e5a59df..1a3dd0e7dd 100644 --- a/crates/hir-expand/src/change.rs +++ b/crates/hir-expand/src/change.rs @@ -48,7 +48,7 @@ impl ChangeWithProcMacros { } } - pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { + pub fn change_file(&mut self, file_id: FileId, new_text: Option) { self.source_change.change_file(file_id, new_text) } diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 6f69ee15ac..ec68f2f96e 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -3,21 +3,19 @@ use base_db::{salsa, CrateId, FileId, SourceDatabase}; use either::Either; use limit::Limit; -use mbe::{syntax_node_to_token_tree, ValueResult}; +use mbe::syntax_node_to_token_tree; use rustc_hash::FxHashSet; -use span::{AstIdMap, SyntaxContextData, SyntaxContextId}; -use syntax::{ - ast::{self, HasAttrs}, - AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, -}; +use span::{AstIdMap, Span, SyntaxContextData, SyntaxContextId}; +use syntax::{ast, AstNode, Parse, SyntaxElement, SyntaxError, SyntaxNode, SyntaxToken, T}; use triomphe::Arc; use crate::{ - attrs::collect_attrs, + attrs::{collect_attrs, AttrId}, builtin_attr_macro::pseudo_derive_attr_expansion, builtin_fn_macro::EagerExpander, + cfg_process, declarative::DeclarativeMacroExpander, - fixup::{self, reverse_fixups, SyntaxFixupUndoInfo}, + fixup::{self, SyntaxFixupUndoInfo}, hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, proc_macro::ProcMacros, span_map::{RealSpanMap, SpanMap, SpanMapRef}, @@ -100,10 +98,7 @@ pub trait ExpandDatabase: SourceDatabase { /// Lowers syntactic macro call to a token tree representation. That's a firewall /// query, only typing in the macro call itself changes the returned /// subtree. - fn macro_arg( - &self, - id: MacroCallId, - ) -> ValueResult<(Arc, SyntaxFixupUndoInfo), Arc>>; + fn macro_arg(&self, id: MacroCallId) -> (Arc, SyntaxFixupUndoInfo, Span); /// Fetches the expander for this macro. #[salsa::transparent] #[salsa::invoke(TokenExpander::macro_expander)] @@ -120,6 +115,12 @@ pub trait ExpandDatabase: SourceDatabase { /// non-determinism breaks salsa in a very, very, very bad way. /// @edwin0cheng heroically debugged this once! See #4315 for details fn expand_proc_macro(&self, call: MacroCallId) -> ExpandResult>; + /// Retrieves the span to be used for a proc-macro expansions spans. + /// This is a firewall query as it requires parsing the file, which we don't want proc-macros to + /// directly depend on as that would cause to frequent invalidations, mainly because of the + /// parse queries being LRU cached. If they weren't the invalidations would only happen if the + /// user wrote in the file that defines the proc-macro. + fn proc_macro_span(&self, fun: AstId) -> Span; /// Firewall query that returns the errors from the `parse_macro_expansion` query. fn parse_macro_expansion_error( &self, @@ -139,30 +140,50 @@ pub fn expand_speculative( ) -> Option<(SyntaxNode, SyntaxToken)> { let loc = db.lookup_intern_macro_call(actual_macro_call); + // FIXME: This BOGUS here is dangerous once the proc-macro server can call back into the database! let span_map = RealSpanMap::absolute(FileId::BOGUS); let span_map = SpanMapRef::RealSpanMap(&span_map); + let (_, _, span) = db.macro_arg(actual_macro_call); + // Build the subtree and token mapping for the speculative args let (mut tt, undo_info) = match loc.kind { MacroCallKind::FnLike { .. } => ( - mbe::syntax_node_to_token_tree(speculative_args, span_map, loc.call_site), + mbe::syntax_node_to_token_tree(speculative_args, span_map, span), SyntaxFixupUndoInfo::NONE, ), - MacroCallKind::Derive { .. } | MacroCallKind::Attr { .. } => { - let censor = censor_for_macro_input(&loc, speculative_args); - let mut fixups = fixup::fixup_syntax(span_map, speculative_args, loc.call_site); + MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => ( + mbe::syntax_node_to_token_tree(speculative_args, span_map, span), + SyntaxFixupUndoInfo::NONE, + ), + MacroCallKind::Derive { derive_attr_index: index, .. } + | MacroCallKind::Attr { invoc_attr_index: index, .. } => { + let censor = if let MacroCallKind::Derive { .. } = loc.kind { + censor_derive_input(index, &ast::Adt::cast(speculative_args.clone())?) + } else { + attr_source(index, &ast::Item::cast(speculative_args.clone())?) + .into_iter() + .map(|it| it.syntax().clone().into()) + .collect() + }; + + let censor_cfg = + cfg_process::process_cfg_attrs(speculative_args, &loc, db).unwrap_or_default(); + let mut fixups = fixup::fixup_syntax(span_map, speculative_args, span); fixups.append.retain(|it, _| match it { - syntax::NodeOrToken::Node(it) => !censor.contains(it), syntax::NodeOrToken::Token(_) => true, + it => !censor.contains(it) && !censor_cfg.contains(it), }); fixups.remove.extend(censor); + fixups.remove.extend(censor_cfg); + ( mbe::syntax_node_to_token_tree_modified( speculative_args, span_map, fixups.append, fixups.remove, - loc.call_site, + span, ), fixups.undo_info, ) @@ -184,9 +205,8 @@ pub fn expand_speculative( }?; match attr.token_tree() { Some(token_tree) => { - let mut tree = - syntax_node_to_token_tree(token_tree.syntax(), span_map, loc.call_site); - tree.delimiter = tt::Delimiter::invisible_spanned(loc.call_site); + let mut tree = syntax_node_to_token_tree(token_tree.syntax(), span_map, span); + tree.delimiter = tt::Delimiter::invisible_spanned(span); Some(tree) } @@ -199,36 +219,36 @@ pub fn expand_speculative( // Do the actual expansion, we need to directly expand the proc macro due to the attribute args // Otherwise the expand query will fetch the non speculative attribute args and pass those instead. let mut speculative_expansion = match loc.def.kind { - MacroDefKind::ProcMacro(expander, ..) => { - tt.delimiter = tt::Delimiter::invisible_spanned(loc.call_site); + MacroDefKind::ProcMacro(expander, _, ast) => { + let span = db.proc_macro_span(ast); + tt.delimiter = tt::Delimiter::invisible_spanned(span); expander.expand( db, loc.def.krate, loc.krate, &tt, attr_arg.as_ref(), - span_with_def_site_ctxt(db, loc.def.span, actual_macro_call), - span_with_call_site_ctxt(db, loc.def.span, actual_macro_call), - span_with_mixed_site_ctxt(db, loc.def.span, actual_macro_call), + span_with_def_site_ctxt(db, span, actual_macro_call), + span_with_call_site_ctxt(db, span, actual_macro_call), + span_with_mixed_site_ctxt(db, span, actual_macro_call), ) } MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => { - pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, loc.call_site) + pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, span) + } + MacroDefKind::Declarative(it) => { + db.decl_macro_expander(loc.krate, it).expand_unhygienic(db, tt, loc.def.krate, span) + } + MacroDefKind::BuiltIn(it, _) => { + it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) } - MacroDefKind::Declarative(it) => db.decl_macro_expander(loc.krate, it).expand_unhygienic( - db, - tt, - loc.def.krate, - loc.call_site, - ), - MacroDefKind::BuiltIn(it, _) => it.expand(db, actual_macro_call, &tt).map_err(Into::into), MacroDefKind::BuiltInDerive(it, ..) => { - it.expand(db, actual_macro_call, &tt).map_err(Into::into) + it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) } MacroDefKind::BuiltInEager(it, _) => { - it.expand(db, actual_macro_call, &tt).map_err(Into::into) + it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) } - MacroDefKind::BuiltInAttr(it, _) => it.expand(db, actual_macro_call, &tt), + MacroDefKind::BuiltInAttr(it, _) => it.expand(db, actual_macro_call, &tt, span), }; let expand_to = loc.expand_to(); @@ -319,181 +339,161 @@ pub(crate) fn parse_with_map( } } -// FIXME: for derive attributes, this will return separate copies of the same structures! +// FIXME: for derive attributes, this will return separate copies of the same structures! Though +// they may differ in spans due to differing call sites... fn macro_arg( db: &dyn ExpandDatabase, id: MacroCallId, - // FIXME: consider the following by putting fixup info into eager call info args - // ) -> ValueResult, Arc>> { -) -> ValueResult<(Arc, SyntaxFixupUndoInfo), Arc>> { +) -> (Arc, SyntaxFixupUndoInfo, Span) { let loc = db.lookup_intern_macro_call(id); - if let Some(EagerCallInfo { arg, .. }) = matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) - .then(|| loc.eager.as_deref()) - .flatten() + + if let MacroCallLoc { + def: MacroDefId { kind: MacroDefKind::BuiltInEager(..), .. }, + kind: MacroCallKind::FnLike { eager: Some(eager), .. }, + .. + } = &loc { - ValueResult::ok((arg.clone(), SyntaxFixupUndoInfo::NONE)) - } else { - let (parse, map) = parse_with_map(db, loc.kind.file_id()); - let root = parse.syntax_node(); - - let syntax = match loc.kind { - MacroCallKind::FnLike { ast_id, .. } => { - let dummy_tt = |kind| { - ( - Arc::new(tt::Subtree { - delimiter: tt::Delimiter { - open: loc.call_site, - close: loc.call_site, - kind, - }, - token_trees: Box::default(), - }), - SyntaxFixupUndoInfo::default(), - ) - }; - - let node = &ast_id.to_ptr(db).to_node(&root); - let offset = node.syntax().text_range().start(); - let Some(tt) = node.token_tree() else { - return ValueResult::new( - dummy_tt(tt::DelimiterKind::Invisible), - Arc::new(Box::new([SyntaxError::new_at_offset( - "missing token tree".to_owned(), - offset, - )])), - ); - }; - let first = tt.left_delimiter_token().map(|it| it.kind()).unwrap_or(T!['(']); - let last = tt.right_delimiter_token().map(|it| it.kind()).unwrap_or(T![.]); - - let mismatched_delimiters = !matches!( - (first, last), - (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) - ); - if mismatched_delimiters { - // Don't expand malformed (unbalanced) macro invocations. This is - // less than ideal, but trying to expand unbalanced macro calls - // sometimes produces pathological, deeply nested code which breaks - // all kinds of things. - // - // So instead, we'll return an empty subtree here - cov_mark::hit!(issue9358_bad_macro_stack_overflow); - - let kind = match first { - _ if loc.def.is_proc_macro() => tt::DelimiterKind::Invisible, - T!['('] => tt::DelimiterKind::Parenthesis, - T!['['] => tt::DelimiterKind::Bracket, - T!['{'] => tt::DelimiterKind::Brace, - _ => tt::DelimiterKind::Invisible, - }; - return ValueResult::new( - dummy_tt(kind), - Arc::new(Box::new([SyntaxError::new_at_offset( - "mismatched delimiters".to_owned(), - offset, - )])), - ); - } - tt.syntax().clone() - } - MacroCallKind::Derive { ast_id, .. } => { - ast_id.to_ptr(db).to_node(&root).syntax().clone() - } - MacroCallKind::Attr { ast_id, .. } => ast_id.to_ptr(db).to_node(&root).syntax().clone(), - }; - let (mut tt, undo_info) = match loc.kind { - MacroCallKind::FnLike { .. } => ( - mbe::syntax_node_to_token_tree(&syntax, map.as_ref(), loc.call_site), - SyntaxFixupUndoInfo::NONE, - ), - MacroCallKind::Derive { .. } | MacroCallKind::Attr { .. } => { - let censor = censor_for_macro_input(&loc, &syntax); - let mut fixups = fixup::fixup_syntax(map.as_ref(), &syntax, loc.call_site); - fixups.append.retain(|it, _| match it { - syntax::NodeOrToken::Node(it) => !censor.contains(it), - syntax::NodeOrToken::Token(_) => true, - }); - fixups.remove.extend(censor); - { - let mut tt = mbe::syntax_node_to_token_tree_modified( - &syntax, - map.as_ref(), - fixups.append.clone(), - fixups.remove.clone(), - loc.call_site, - ); - reverse_fixups(&mut tt, &fixups.undo_info); - } - ( - mbe::syntax_node_to_token_tree_modified( - &syntax, - map, - fixups.append, - fixups.remove, - loc.call_site, - ), - fixups.undo_info, - ) - } - }; - - if loc.def.is_proc_macro() { - // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.delimiter.kind = tt::DelimiterKind::Invisible; - } - - if matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) { - match parse.errors() { - errors if errors.is_empty() => ValueResult::ok((Arc::new(tt), undo_info)), - errors => ValueResult::new( - (Arc::new(tt), undo_info), - // Box::<[_]>::from(res.errors()), not stable yet - Arc::new(errors.to_vec().into_boxed_slice()), - ), - } - } else { - ValueResult::ok((Arc::new(tt), undo_info)) - } + return (eager.arg.clone(), SyntaxFixupUndoInfo::NONE, eager.span); } + + let (parse, map) = parse_with_map(db, loc.kind.file_id()); + let root = parse.syntax_node(); + + let (censor, item_node, span) = match loc.kind { + MacroCallKind::FnLike { ast_id, .. } => { + let node = &ast_id.to_ptr(db).to_node(&root); + let path_range = node + .path() + .map_or_else(|| node.syntax().text_range(), |path| path.syntax().text_range()); + let span = map.span_for_range(path_range); + + let dummy_tt = |kind| { + ( + Arc::new(tt::Subtree { + delimiter: tt::Delimiter { open: span, close: span, kind }, + token_trees: Box::default(), + }), + SyntaxFixupUndoInfo::default(), + span, + ) + }; + + let Some(tt) = node.token_tree() else { + return dummy_tt(tt::DelimiterKind::Invisible); + }; + let first = tt.left_delimiter_token().map(|it| it.kind()).unwrap_or(T!['(']); + let last = tt.right_delimiter_token().map(|it| it.kind()).unwrap_or(T![.]); + + let mismatched_delimiters = !matches!( + (first, last), + (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) + ); + if mismatched_delimiters { + // Don't expand malformed (unbalanced) macro invocations. This is + // less than ideal, but trying to expand unbalanced macro calls + // sometimes produces pathological, deeply nested code which breaks + // all kinds of things. + // + // So instead, we'll return an empty subtree here + cov_mark::hit!(issue9358_bad_macro_stack_overflow); + + let kind = match first { + _ if loc.def.is_proc_macro() => tt::DelimiterKind::Invisible, + T!['('] => tt::DelimiterKind::Parenthesis, + T!['['] => tt::DelimiterKind::Bracket, + T!['{'] => tt::DelimiterKind::Brace, + _ => tt::DelimiterKind::Invisible, + }; + return dummy_tt(kind); + } + + let mut tt = mbe::syntax_node_to_token_tree(tt.syntax(), map.as_ref(), span); + if loc.def.is_proc_macro() { + // proc macros expect their inputs without parentheses, MBEs expect it with them included + tt.delimiter.kind = tt::DelimiterKind::Invisible; + } + return (Arc::new(tt), SyntaxFixupUndoInfo::NONE, span); + } + MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { + let node = ast_id.to_ptr(db).to_node(&root); + let censor_derive_input = censor_derive_input(derive_attr_index, &node); + let item_node = node.into(); + let attr_source = attr_source(derive_attr_index, &item_node); + // FIXME: This is wrong, this should point to the path of the derive attribute` + let span = + map.span_for_range(attr_source.as_ref().and_then(|it| it.path()).map_or_else( + || item_node.syntax().text_range(), + |it| it.syntax().text_range(), + )); + (censor_derive_input, item_node, span) + } + MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + let node = ast_id.to_ptr(db).to_node(&root); + let attr_source = attr_source(invoc_attr_index, &node); + let span = map.span_for_range( + attr_source + .as_ref() + .and_then(|it| it.path()) + .map_or_else(|| node.syntax().text_range(), |it| it.syntax().text_range()), + ); + (attr_source.into_iter().map(|it| it.syntax().clone().into()).collect(), node, span) + } + }; + + let (mut tt, undo_info) = { + let syntax = item_node.syntax(); + let censor_cfg = cfg_process::process_cfg_attrs(syntax, &loc, db).unwrap_or_default(); + let mut fixups = fixup::fixup_syntax(map.as_ref(), syntax, span); + fixups.append.retain(|it, _| match it { + syntax::NodeOrToken::Token(_) => true, + it => !censor.contains(it) && !censor_cfg.contains(it), + }); + fixups.remove.extend(censor); + fixups.remove.extend(censor_cfg); + + ( + mbe::syntax_node_to_token_tree_modified( + syntax, + map, + fixups.append, + fixups.remove, + span, + ), + fixups.undo_info, + ) + }; + + if loc.def.is_proc_macro() { + // proc macros expect their inputs without parentheses, MBEs expect it with them included + tt.delimiter.kind = tt::DelimiterKind::Invisible; + } + + (Arc::new(tt), undo_info, span) } // FIXME: Censoring info should be calculated by the caller! Namely by name resolution -/// Certain macro calls expect some nodes in the input to be preprocessed away, namely: -/// - derives expect all `#[derive(..)]` invocations up to the currently invoked one to be stripped -/// - attributes expect the invoking attribute to be stripped -fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet { +/// Derives expect all `#[derive(..)]` invocations up to (and including) the currently invoked one to be stripped +fn censor_derive_input(derive_attr_index: AttrId, node: &ast::Adt) -> FxHashSet { // FIXME: handle `cfg_attr` - (|| { - let censor = match loc.kind { - MacroCallKind::FnLike { .. } => return None, - MacroCallKind::Derive { derive_attr_index, .. } => { - cov_mark::hit!(derive_censoring); - ast::Item::cast(node.clone())? - .attrs() - .take(derive_attr_index.ast_index() + 1) - // FIXME, this resolution should not be done syntactically - // derive is a proper macro now, no longer builtin - // But we do not have resolution at this stage, this means - // we need to know about all macro calls for the given ast item here - // so we require some kind of mapping... - .filter(|attr| attr.simple_name().as_deref() == Some("derive")) - .map(|it| it.syntax().clone()) - .collect() - } - MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => return None, - MacroCallKind::Attr { invoc_attr_index, .. } => { - cov_mark::hit!(attribute_macro_attr_censoring); - collect_attrs(&ast::Item::cast(node.clone())?) - .nth(invoc_attr_index.ast_index()) - .and_then(|x| Either::left(x.1)) - .map(|attr| attr.syntax().clone()) - .into_iter() - .collect() - } - }; - Some(censor) - })() - .unwrap_or_default() + cov_mark::hit!(derive_censoring); + collect_attrs(node) + .take(derive_attr_index.ast_index() + 1) + .filter_map(|(_, attr)| Either::left(attr)) + // FIXME, this resolution should not be done syntactically + // derive is a proper macro now, no longer builtin + // But we do not have resolution at this stage, this means + // we need to know about all macro calls for the given ast item here + // so we require some kind of mapping... + .filter(|attr| attr.simple_name().as_deref() == Some("derive")) + .map(|it| it.syntax().clone().into()) + .collect() +} + +/// Attributes expect the invoking attribute to be stripped +fn attr_source(invoc_attr_index: AttrId, node: &ast::Item) -> Option { + // FIXME: handle `cfg_attr` + cov_mark::hit!(attribute_macro_attr_censoring); + collect_attrs(node).nth(invoc_attr_index.ast_index()).and_then(|(_, attr)| Either::left(attr)) } impl TokenExpander { @@ -523,74 +523,64 @@ fn macro_expand( ) -> ExpandResult> { let _p = tracing::span!(tracing::Level::INFO, "macro_expand").entered(); - let ExpandResult { value: tt, mut err } = match loc.def.kind { + let (ExpandResult { value: tt, err }, span) = match loc.def.kind { MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id).map(CowArc::Arc), _ => { - let ValueResult { value: (macro_arg, undo_info), err } = db.macro_arg(macro_call_id); - let format_parse_err = |err: Arc>| { - let mut buf = String::new(); - for err in &**err { - use std::fmt::Write; - _ = write!(buf, "{}, ", err); - } - buf.pop(); - buf.pop(); - ExpandError::other(buf) - }; + let (macro_arg, undo_info, span) = db.macro_arg(macro_call_id); let arg = &*macro_arg; - let res = match loc.def.kind { - MacroDefKind::Declarative(id) => { - db.decl_macro_expander(loc.def.krate, id).expand(db, arg.clone(), macro_call_id) - } - MacroDefKind::BuiltIn(it, _) => { - it.expand(db, macro_call_id, arg).map_err(Into::into) - } - // This might look a bit odd, but we do not expand the inputs to eager macros here. - // Eager macros inputs are expanded, well, eagerly when we collect the macro calls. - // That kind of expansion uses the ast id map of an eager macros input though which goes through - // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query - // will end up going through here again, whereas we want to just want to inspect the raw input. - // As such we just return the input subtree here. - MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => { - return ExpandResult { - value: CowArc::Arc(macro_arg.clone()), - err: err.map(format_parse_err), - }; - } - MacroDefKind::BuiltInDerive(it, _) => { - it.expand(db, macro_call_id, arg).map_err(Into::into) - } - MacroDefKind::BuiltInEager(it, _) => { - it.expand(db, macro_call_id, arg).map_err(Into::into) - } - MacroDefKind::BuiltInAttr(it, _) => { - let mut res = it.expand(db, macro_call_id, arg); - fixup::reverse_fixups(&mut res.value, &undo_info); - res - } - _ => unreachable!(), - }; - ExpandResult { - value: res.value, - // if the arg had parse errors, show them instead of the expansion errors - err: err.map(format_parse_err).or(res.err), - } + let res = + match loc.def.kind { + MacroDefKind::Declarative(id) => db + .decl_macro_expander(loc.def.krate, id) + .expand(db, arg.clone(), macro_call_id, span), + MacroDefKind::BuiltIn(it, _) => { + it.expand(db, macro_call_id, arg, span).map_err(Into::into) + } + MacroDefKind::BuiltInDerive(it, _) => { + it.expand(db, macro_call_id, arg, span).map_err(Into::into) + } + MacroDefKind::BuiltInEager(it, _) => { + // This might look a bit odd, but we do not expand the inputs to eager macros here. + // Eager macros inputs are expanded, well, eagerly when we collect the macro calls. + // That kind of expansion uses the ast id map of an eager macros input though which goes through + // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query + // will end up going through here again, whereas we want to just want to inspect the raw input. + // As such we just return the input subtree here. + let eager = match &loc.kind { + MacroCallKind::FnLike { eager: None, .. } => { + return ExpandResult::ok(CowArc::Arc(macro_arg.clone())); + } + MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager), + _ => None, + }; + + let mut res = it.expand(db, macro_call_id, arg, span).map_err(Into::into); + + if let Some(EagerCallInfo { error, .. }) = eager { + // FIXME: We should report both errors! + res.err = error.clone().or(res.err); + } + res + } + MacroDefKind::BuiltInAttr(it, _) => { + let mut res = it.expand(db, macro_call_id, arg, span); + fixup::reverse_fixups(&mut res.value, &undo_info); + res + } + _ => unreachable!(), + }; + (ExpandResult { value: res.value, err: res.err }, span) } }; - if let Some(EagerCallInfo { error, .. }) = loc.eager.as_deref() { - // FIXME: We should report both errors! - err = error.clone().or(err); - } - // Skip checking token tree limit for include! macro call if !loc.def.is_include() { // Set a hard limit for the expanded tt if let Err(value) = check_tt_count(&tt) { return value.map(|()| { CowArc::Owned(tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(loc.call_site), + delimiter: tt::Delimiter::invisible_spanned(span), token_trees: Box::new([]), }) }); @@ -600,12 +590,23 @@ fn macro_expand( ExpandResult { value: CowArc::Owned(tt), err } } +fn proc_macro_span(db: &dyn ExpandDatabase, ast: AstId) -> Span { + let root = db.parse_or_expand(ast.file_id); + let ast_id_map = &db.ast_id_map(ast.file_id); + let span_map = &db.span_map(ast.file_id); + + let node = ast_id_map.get(ast.value).to_node(&root); + let range = ast::HasName::name(&node) + .map_or_else(|| node.syntax().text_range(), |name| name.syntax().text_range()); + span_map.span_for_range(range) +} + fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult> { let loc = db.lookup_intern_macro_call(id); - let (macro_arg, undo_info) = db.macro_arg(id).value; + let (macro_arg, undo_info, span) = db.macro_arg(id); - let expander = match loc.def.kind { - MacroDefKind::ProcMacro(expander, ..) => expander, + let (expander, ast) = match loc.def.kind { + MacroDefKind::ProcMacro(expander, _, ast) => (expander, ast), _ => unreachable!(), }; @@ -614,22 +615,25 @@ fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult None, }; - let ExpandResult { value: mut tt, err } = expander.expand( - db, - loc.def.krate, - loc.krate, - ¯o_arg, - attr_arg, - span_with_def_site_ctxt(db, loc.def.span, id), - span_with_call_site_ctxt(db, loc.def.span, id), - span_with_mixed_site_ctxt(db, loc.def.span, id), - ); + let ExpandResult { value: mut tt, err } = { + let span = db.proc_macro_span(ast); + expander.expand( + db, + loc.def.krate, + loc.krate, + ¯o_arg, + attr_arg, + span_with_def_site_ctxt(db, span, id), + span_with_call_site_ctxt(db, span, id), + span_with_mixed_site_ctxt(db, span, id), + ) + }; // Set a hard limit for the expanded tt if let Err(value) = check_tt_count(&tt) { return value.map(|()| { Arc::new(tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(loc.call_site), + delimiter: tt::Delimiter::invisible_spanned(span), token_trees: Box::new([]), }) }); diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs index 6874336cd2..33643c0272 100644 --- a/crates/hir-expand/src/declarative.rs +++ b/crates/hir-expand/src/declarative.rs @@ -29,6 +29,7 @@ impl DeclarativeMacroExpander { db: &dyn ExpandDatabase, tt: tt::Subtree, call_id: MacroCallId, + span: Span, ) -> ExpandResult { let loc = db.lookup_intern_macro_call(call_id); let toolchain = db.toolchain(loc.def.krate); @@ -45,7 +46,7 @@ impl DeclarativeMacroExpander { }); match self.mac.err() { Some(_) => ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }), + tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), ExpandError::MacroDefinition, ), None => self @@ -54,7 +55,7 @@ impl DeclarativeMacroExpander { &tt, |s| s.ctx = apply_mark(db, s.ctx, call_id, self.transparency), new_meta_vars, - loc.call_site, + span, ) .map_err(Into::into), } diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs index 5337a5bb02..8b147c88c1 100644 --- a/crates/hir-expand/src/eager.rs +++ b/crates/hir-expand/src/eager.rs @@ -19,7 +19,7 @@ //! //! See the full discussion : use base_db::CrateId; -use span::Span; +use span::SyntaxContextId; use syntax::{ted, Parse, SyntaxElement, SyntaxNode, TextSize, WalkEvent}; use triomphe::Arc; @@ -27,22 +27,20 @@ use crate::{ ast::{self, AstNode}, db::ExpandDatabase, mod_path::ModPath, - EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, Intern, + AstId, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, Intern, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, }; pub fn expand_eager_macro_input( db: &dyn ExpandDatabase, krate: CrateId, - macro_call: InFile, + macro_call: &ast::MacroCall, + ast_id: AstId, def: MacroDefId, - call_site: Span, + call_site: SyntaxContextId, resolver: &dyn Fn(ModPath) -> Option, ) -> ExpandResult> { - let ast_map = db.ast_id_map(macro_call.file_id); - // the expansion which the ast id map is built upon has no whitespace, so the offsets are wrong as macro_call is from the token tree that has whitespace! - let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(¯o_call.value)); - let expand_to = ExpandTo::from_call_site(¯o_call.value); + let expand_to = ExpandTo::from_call_site(macro_call); // Note: // When `lazy_expand` is called, its *parent* file must already exist. @@ -51,11 +49,11 @@ pub fn expand_eager_macro_input( let arg_id = MacroCallLoc { def, krate, - eager: None, - kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr }, - call_site, + kind: MacroCallKind::FnLike { ast_id, expand_to: ExpandTo::Expr, eager: None }, + ctxt: call_site, } .intern(db); + let (_, _, span) = db.macro_arg(arg_id); let ExpandResult { value: (arg_exp, arg_exp_map), err: parse_err } = db.parse_macro_expansion(arg_id.as_macro_file()); @@ -82,16 +80,24 @@ pub fn expand_eager_macro_input( return ExpandResult { value: None, err }; }; - let mut subtree = mbe::syntax_node_to_token_tree(&expanded_eager_input, arg_map, call_site); + let mut subtree = mbe::syntax_node_to_token_tree(&expanded_eager_input, arg_map, span); subtree.delimiter.kind = crate::tt::DelimiterKind::Invisible; let loc = MacroCallLoc { def, krate, - eager: Some(Arc::new(EagerCallInfo { arg: Arc::new(subtree), arg_id, error: err.clone() })), - kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, - call_site, + kind: MacroCallKind::FnLike { + ast_id, + expand_to, + eager: Some(Arc::new(EagerCallInfo { + arg: Arc::new(subtree), + arg_id, + error: err.clone(), + span, + })), + }, + ctxt: call_site, }; ExpandResult { value: Some(loc.intern(db)), err } @@ -100,15 +106,18 @@ pub fn expand_eager_macro_input( fn lazy_expand( db: &dyn ExpandDatabase, def: &MacroDefId, - macro_call: InFile, + macro_call: &ast::MacroCall, + ast_id: AstId, krate: CrateId, - call_site: Span, + call_site: SyntaxContextId, ) -> ExpandResult<(InFile>, Arc)> { - let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); - - let expand_to = ExpandTo::from_call_site(¯o_call.value); - let ast_id = macro_call.with_value(ast_id); - let id = def.as_lazy_macro(db, krate, MacroCallKind::FnLike { ast_id, expand_to }, call_site); + let expand_to = ExpandTo::from_call_site(macro_call); + let id = def.make_call( + db, + krate, + MacroCallKind::FnLike { ast_id, expand_to, eager: None }, + call_site, + ); let macro_file = id.as_macro_file(); db.parse_macro_expansion(macro_file) @@ -122,7 +131,7 @@ fn eager_macro_recur( mut offset: TextSize, curr: InFile, krate: CrateId, - call_site: Span, + call_site: SyntaxContextId, macro_resolver: &dyn Fn(ModPath) -> Option, ) -> ExpandResult> { let original = curr.value.clone_for_update(); @@ -172,12 +181,14 @@ fn eager_macro_recur( continue; } }; + let ast_id = db.ast_id_map(curr.file_id).ast_id(&call); let ExpandResult { value, err } = match def.kind { MacroDefKind::BuiltInEager(..) => { let ExpandResult { value, err } = expand_eager_macro_input( db, krate, - curr.with_value(call.clone()), + &call, + curr.with_value(ast_id), def, call_site, macro_resolver, @@ -207,7 +218,7 @@ fn eager_macro_recur( | MacroDefKind::BuiltInDerive(..) | MacroDefKind::ProcMacro(..) => { let ExpandResult { value: (parse, tm), err } = - lazy_expand(db, &def, curr.with_value(call.clone()), krate, call_site); + lazy_expand(db, &def, &call, curr.with_value(ast_id), krate, call_site); // replace macro inside let ExpandResult { value, err: error } = eager_macro_recur( diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index a500c24ce8..04a4851ddb 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -10,7 +10,7 @@ use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, use crate::{ db::{self, ExpandDatabase}, - map_node_range_up, span_for_offset, MacroFileIdExt, + map_node_range_up, map_node_range_up_rooted, span_for_offset, MacroFileIdExt, }; /// `InFile` stores a value of `T` inside a particular file/syntax tree. @@ -38,6 +38,9 @@ impl AstId { pub fn to_node(&self, db: &dyn ExpandDatabase) -> N { self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)) } + pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange { + self.to_ptr(db).text_range() + } pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile { crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))) } @@ -49,6 +52,9 @@ impl AstId { pub type ErasedAstId = crate::InFile; impl ErasedAstId { + pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange { + self.to_ptr(db).text_range() + } pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr { db.ast_id_map(self.file_id).get_erased(self.value) } @@ -173,24 +179,8 @@ impl InFile<&SyntaxNode> { /// /// For attributes and derives, this will point back to the attribute only. /// For the entire item use [`InFile::original_file_range_full`]. - pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange { - match self.file_id.repr() { - HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, - HirFileIdRepr::MacroFile(mac_file) => { - if let Some((res, ctxt)) = - map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range()) - { - // FIXME: Figure out an API that makes proper use of ctx, this only exists to - // keep pre-token map rewrite behaviour. - if ctxt.is_root() { - return res; - } - } - // Fall back to whole macro call. - let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); - loc.kind.original_call_range(db) - } - } + pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange { + self.map(SyntaxNode::text_range).original_node_file_range_rooted(db) } /// Falls back to the macro call range if the node cannot be mapped up fully. @@ -198,23 +188,7 @@ impl InFile<&SyntaxNode> { self, db: &dyn db::ExpandDatabase, ) -> FileRange { - match self.file_id.repr() { - HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, - HirFileIdRepr::MacroFile(mac_file) => { - if let Some((res, ctxt)) = - map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range()) - { - // FIXME: Figure out an API that makes proper use of ctx, this only exists to - // keep pre-token map rewrite behaviour. - if ctxt.is_root() { - return res; - } - } - // Fall back to whole macro call. - let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); - loc.kind.original_call_range_with_body(db) - } - } + self.map(SyntaxNode::text_range).original_node_file_range_with_macro_call_body(db) } /// Attempts to map the syntax node back up its macro calls. @@ -222,17 +196,10 @@ impl InFile<&SyntaxNode> { self, db: &dyn db::ExpandDatabase, ) -> Option<(FileRange, SyntaxContextId)> { - match self.file_id.repr() { - HirFileIdRepr::FileId(file_id) => { - Some((FileRange { file_id, range: self.value.text_range() }, SyntaxContextId::ROOT)) - } - HirFileIdRepr::MacroFile(mac_file) => { - map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range()) - } - } + self.map(SyntaxNode::text_range).original_node_file_range_opt(db) } - pub fn original_syntax_node( + pub fn original_syntax_node_rooted( self, db: &dyn db::ExpandDatabase, ) -> Option> { @@ -242,25 +209,21 @@ impl InFile<&SyntaxNode> { HirFileIdRepr::FileId(file_id) => { return Some(InRealFile { file_id, value: self.value.clone() }) } - HirFileIdRepr::MacroFile(m) => m, + HirFileIdRepr::MacroFile(m) if m.is_attr_macro(db) => m, + _ => return None, }; - if !file_id.is_attr_macro(db) { - return None; - } - let (FileRange { file_id, range }, ctx) = - map_node_range_up(db, &db.expansion_span_map(file_id), self.value.text_range())?; + let FileRange { file_id, range } = + map_node_range_up_rooted(db, &db.expansion_span_map(file_id), self.value.text_range())?; - // FIXME: Figure out an API that makes proper use of ctx, this only exists to - // keep pre-token map rewrite behavior. - if !ctx.is_root() { - return None; - } - - let anc = db.parse(file_id).syntax_node().covering_element(range); let kind = self.value.kind(); - // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes? - let value = anc.ancestors().find(|it| it.kind() == kind)?; + let value = db + .parse(file_id) + .syntax_node() + .covering_element(range) + .ancestors() + .take_while(|it| it.text_range() == range) + .find(|it| it.kind() == kind)?; Some(InRealFile::new(file_id, value)) } } @@ -355,8 +318,8 @@ impl InFile { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value }, HirFileIdRepr::MacroFile(mac_file) => { - match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) { - Some((it, SyntaxContextId::ROOT)) => it, + match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) { + Some(it) => it, _ => { let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); loc.kind.original_call_range(db) @@ -366,6 +329,24 @@ impl InFile { } } + pub fn original_node_file_range_with_macro_call_body( + self, + db: &dyn db::ExpandDatabase, + ) -> FileRange { + match self.file_id.repr() { + HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value }, + HirFileIdRepr::MacroFile(mac_file) => { + match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) { + Some(it) => it, + _ => { + let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); + loc.kind.original_call_range_with_body(db) + } + } + } + } + } + pub fn original_node_file_range_opt( self, db: &dyn db::ExpandDatabase, @@ -395,18 +376,12 @@ impl InFile { return None; } - let (FileRange { file_id, range }, ctx) = map_node_range_up( + let FileRange { file_id, range } = map_node_range_up_rooted( db, &db.expansion_span_map(file_id), self.value.syntax().text_range(), )?; - // FIXME: Figure out an API that makes proper use of ctx, this only exists to - // keep pre-token map rewrite behaviour. - if !ctx.is_root() { - return None; - } - // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes? let anc = db.parse(file_id).syntax_node().covering_element(range); let value = anc.ancestors().find_map(N::cast)?; diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index a3b2c700ff..959595afb5 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -3,7 +3,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::SmallVec; -use span::{ErasedFileAstId, Span, SpanAnchor, SpanData, FIXUP_ERASED_FILE_AST_ID_MARKER}; +use span::{ErasedFileAstId, Span, SpanAnchor, FIXUP_ERASED_FILE_AST_ID_MARKER}; use stdx::never; use syntax::{ ast::{self, AstNode, HasLoopBody}, @@ -23,7 +23,7 @@ use crate::{ #[derive(Debug, Default)] pub(crate) struct SyntaxFixups { pub(crate) append: FxHashMap>, - pub(crate) remove: FxHashSet, + pub(crate) remove: FxHashSet, pub(crate) undo_info: SyntaxFixupUndoInfo, } @@ -51,13 +51,13 @@ pub(crate) fn fixup_syntax( call_site: Span, ) -> SyntaxFixups { let mut append = FxHashMap::::default(); - let mut remove = FxHashSet::::default(); + let mut remove = FxHashSet::::default(); let mut preorder = node.preorder(); let mut original = Vec::new(); let dummy_range = FIXUP_DUMMY_RANGE; let fake_span = |range| { let span = span_map.span_for_range(range); - SpanData { + Span { range: dummy_range, anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor }, ctx: span.ctx, @@ -68,7 +68,7 @@ pub(crate) fn fixup_syntax( let node_range = node.text_range(); if can_handle_error(&node) && has_error_to_handle(&node) { - remove.insert(node.clone()); + remove.insert(node.clone().into()); // the node contains an error node, we have to completely replace it by something valid let original_tree = mbe::syntax_node_to_token_tree(&node, span_map, call_site); let idx = original.len() as u32; @@ -76,7 +76,7 @@ pub(crate) fn fixup_syntax( let span = span_map.span_for_range(node_range); let replacement = Leaf::Ident(Ident { text: "__ra_fixup".into(), - span: SpanData { + span: Span { range: TextRange::new(TextSize::new(idx), FIXUP_DUMMY_RANGE_END), anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor }, ctx: span.ctx, @@ -305,8 +305,8 @@ pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo) tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID ) { - tt.delimiter.close = SpanData::DUMMY; - tt.delimiter.open = SpanData::DUMMY; + tt.delimiter.close = Span::DUMMY; + tt.delimiter.open = Span::DUMMY; } reverse_fixups_(tt, undo_info); } diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index ac2bab280d..097e760c70 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -65,7 +65,7 @@ pub(super) fn apply_mark( return apply_mark_internal(db, ctxt, call_id, transparency); } - let call_site_ctxt = db.lookup_intern_macro_call(call_id).call_site.ctx; + let call_site_ctxt = db.lookup_intern_macro_call(call_id).ctxt; let mut call_site_ctxt = if transparency == Transparency::SemiTransparent { call_site_ctxt.normalize_to_macros_2_0(db) } else { @@ -205,11 +205,10 @@ pub(crate) fn dump_syntax_contexts(db: &dyn ExpandDatabase) -> String { let id = e.key; let expn_data = e.value.as_ref().unwrap(); s.push_str(&format!( - "\n{:?}: parent: {:?}, call_site_ctxt: {:?}, def_site_ctxt: {:?}, kind: {:?}", + "\n{:?}: parent: {:?}, call_site_ctxt: {:?}, kind: {:?}", id, expn_data.kind.file_id(), - expn_data.call_site, - SyntaxContextId::ROOT, // FIXME expn_data.def_site, + expn_data.ctxt, expn_data.kind.descr(), )); } diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 42dc8c12d6..5d4f7dc146 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -22,16 +22,19 @@ pub mod proc_macro; pub mod quote; pub mod span_map; +mod cfg_process; mod fixup; - use attrs::collect_attrs; +use rustc_hash::FxHashMap; use triomphe::Arc; use std::{fmt, hash::Hash}; use base_db::{salsa::impl_intern_value_trivial, CrateId, Edition, FileId}; use either::Either; -use span::{ErasedFileAstId, FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId}; +use span::{ + ErasedFileAstId, FileRange, HirFileIdRepr, Span, SpanAnchor, SyntaxContextData, SyntaxContextId, +}; use syntax::{ ast::{self, AstNode}, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -167,13 +170,8 @@ impl fmt::Display for ExpandError { pub struct MacroCallLoc { pub def: MacroDefId, pub krate: CrateId, - /// Some if this is a macro call for an eager macro. Note that this is `None` - /// for the eager input macro file. - // FIXME: This is being interned, subtrees can vary quickly differ just slightly causing - // leakage problems here - eager: Option>, pub kind: MacroCallKind, - pub call_site: Span, + pub ctxt: SyntaxContextId, } impl_intern_value_trivial!(MacroCallLoc); @@ -184,7 +182,6 @@ pub struct MacroDefId { pub kind: MacroDefKind, pub local_inner: bool, pub allow_internal_unsafe: bool, - pub span: Span, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -204,6 +201,8 @@ pub struct EagerCallInfo { /// Call id of the eager macro's input file (this is the macro file for its fully expanded input). arg_id: MacroCallId, error: Option, + /// TODO: Doc + span: Span, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -211,6 +210,11 @@ pub enum MacroCallKind { FnLike { ast_id: AstId, expand_to: ExpandTo, + /// Some if this is a macro call for an eager macro. Note that this is `None` + /// for the eager input macro file. + // FIXME: This is being interned, subtrees can vary quickly differ just slightly causing + // leakage problems here + eager: Option>, }, Derive { ast_id: AstId, @@ -272,7 +276,7 @@ impl HirFileIdExt for HirFileId { HirFileIdRepr::MacroFile(file) => { let loc = db.lookup_intern_macro_call(file.macro_call_id); if loc.def.is_include() { - if let Some(eager) = &loc.eager { + if let MacroCallKind::FnLike { eager: Some(eager), .. } = &loc.kind { if let Ok(it) = builtin_fn_macro::include_input_to_file_id( db, file.macro_call_id, @@ -319,6 +323,9 @@ impl HirFileIdExt for HirFileId { } pub trait MacroFileIdExt { + fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool; + fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool; + fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option; fn expansion_level(self, db: &dyn ExpandDatabase) -> u32; /// If this is a macro call, returns the syntax node of the call. fn call_node(self, db: &dyn ExpandDatabase) -> InFile; @@ -385,31 +392,47 @@ impl MacroFileIdExt for MacroFileId { db.lookup_intern_macro_call(self.macro_call_id).def.is_include() } + fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool { + db.lookup_intern_macro_call(self.macro_call_id).def.is_include_like() + } + + fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool { + db.lookup_intern_macro_call(self.macro_call_id).def.is_env_or_option_env() + } + fn is_eager(&self, db: &dyn ExpandDatabase) -> bool { - let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); + let loc = db.lookup_intern_macro_call(self.macro_call_id); matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) } + fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option { + let loc = db.lookup_intern_macro_call(self.macro_call_id); + match &loc.kind { + MacroCallKind::FnLike { eager, .. } => eager.as_ref().map(|it| it.arg_id), + _ => None, + } + } + fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool { - let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); + let loc = db.lookup_intern_macro_call(self.macro_call_id); matches!(loc.kind, MacroCallKind::Attr { .. }) } fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool { - let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); + let loc = db.lookup_intern_macro_call(self.macro_call_id); loc.def.is_attribute_derive() } } impl MacroDefId { - pub fn as_lazy_macro( + pub fn make_call( self, db: &dyn ExpandDatabase, krate: CrateId, kind: MacroCallKind, - call_site: Span, + ctxt: SyntaxContextId, ) -> MacroCallId { - MacroCallLoc { def: self, krate, eager: None, kind, call_site }.intern(db) + MacroCallLoc { def: self, krate, kind, ctxt }.intern(db) } pub fn definition_range(&self, db: &dyn ExpandDatabase) -> InFile { @@ -474,6 +497,14 @@ impl MacroDefId { pub fn is_include(&self) -> bool { matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_include()) } + + pub fn is_include_like(&self) -> bool { + matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_include_like()) + } + + pub fn is_env_or_option_env(&self) -> bool { + matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_env_or_option_env()) + } } impl MacroCallLoc { @@ -531,7 +562,7 @@ impl MacroCallLoc { macro_call_id: MacroCallId, ) -> Option { if self.def.is_include() { - if let Some(eager) = &self.eager { + if let MacroCallKind::FnLike { eager: Some(eager), .. } = &self.kind { if let Ok(it) = builtin_fn_macro::include_input_to_file_id(db, macro_call_id, &eager.arg) { @@ -655,7 +686,7 @@ impl MacroCallKind { /// ExpansionInfo mainly describes how to map text range between src and expanded macro // FIXME: can be expensive to create, we should check the use sites and maybe replace them with // simpler function calls if the map is only used once -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ExpansionInfo { pub expanded: InMacroFile, /// The argument TokenTree or item for attributes @@ -683,6 +714,24 @@ impl ExpansionInfo { } /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. + /// + /// Note this does a linear search through the entire backing vector of the spanmap. + pub fn map_range_down_exact( + &self, + span: Span, + ) -> Option + '_>> { + let tokens = self + .exp_map + .ranges_with_span_exact(span) + .flat_map(move |range| self.expanded.value.covering_element(range).into_token()); + + Some(InMacroFile::new(self.expanded.file_id, tokens)) + } + + /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. + /// Unlike [`map_range_down_exact`], this will consider spans that contain the given span. + /// + /// Note this does a linear search through the entire backing vector of the spanmap. pub fn map_range_down( &self, span: Span, @@ -739,7 +788,7 @@ impl ExpansionInfo { InFile::new( self.arg.file_id, arg_map - .ranges_with_span(span) + .ranges_with_span_exact(span) .filter(|range| range.intersect(arg_range).is_some()) .collect(), ) @@ -757,7 +806,7 @@ impl ExpansionInfo { let (parse, exp_map) = db.parse_macro_expansion(macro_file).value; let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() }; - let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value; + let (macro_arg, _, _) = db.macro_arg(macro_file.macro_call_id); let def = loc.def.ast_id().left().and_then(|id| { let def_tt = match id.to_node(db) { @@ -793,7 +842,34 @@ impl ExpansionInfo { } } +/// Maps up the text range out of the expansion hierarchy back into the original file its from only +/// considering the root spans contained. +/// Unlike [`map_node_range_up`], this will not return `None` if any anchors or syntax contexts differ. +pub fn map_node_range_up_rooted( + db: &dyn ExpandDatabase, + exp_map: &ExpansionSpanMap, + range: TextRange, +) -> Option { + let mut spans = exp_map.spans_for_range(range).filter(|span| span.ctx.is_root()); + let Span { range, anchor, ctx: _ } = spans.next()?; + let mut start = range.start(); + let mut end = range.end(); + + for span in spans { + if span.anchor != anchor { + return None; + } + start = start.min(span.range.start()); + end = end.max(span.range.end()); + } + let anchor_offset = + db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start(); + Some(FileRange { file_id: anchor.file_id, range: TextRange::new(start, end) + anchor_offset }) +} + /// Maps up the text range out of the expansion hierarchy back into the original file its from. +/// +/// this will return `None` if any anchors or syntax contexts differ. pub fn map_node_range_up( db: &dyn ExpandDatabase, exp_map: &ExpansionSpanMap, @@ -819,6 +895,29 @@ pub fn map_node_range_up( )) } +/// Maps up the text range out of the expansion hierarchy back into the original file its from. +/// This version will aggregate the ranges of all spans with the same anchor and syntax context. +pub fn map_node_range_up_aggregated( + db: &dyn ExpandDatabase, + exp_map: &ExpansionSpanMap, + range: TextRange, +) -> FxHashMap<(SpanAnchor, SyntaxContextId), TextRange> { + let mut map = FxHashMap::default(); + for span in exp_map.spans_for_range(range) { + let range = map.entry((span.anchor, span.ctx)).or_insert_with(|| span.range); + *range = TextRange::new( + range.start().min(span.range.start()), + range.end().max(span.range.end()), + ); + } + for ((anchor, _), range) in &mut map { + let anchor_offset = + db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start(); + *range += anchor_offset; + } + map +} + /// Looks up the span at the given offset. pub fn span_for_offset( db: &dyn ExpandDatabase, diff --git a/crates/hir-expand/src/quote.rs b/crates/hir-expand/src/quote.rs index c1930c94f5..a31a111c91 100644 --- a/crates/hir-expand/src/quote.rs +++ b/crates/hir-expand/src/quote.rs @@ -266,10 +266,11 @@ mod tests { let quoted = quote!(DUMMY =>#a); assert_eq!(quoted.to_string(), "hello"); - let t = format!("{quoted:?}"); + let t = format!("{quoted:#?}"); expect![[r#" - SUBTREE $$ SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) } SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) } - IDENT hello SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) }"#]].assert_eq(&t); + SUBTREE $$ 937550:0@0..0#0 937550:0@0..0#0 + IDENT hello 937550:0@0..0#0"#]] + .assert_eq(&t); } #[test] diff --git a/crates/hir-expand/src/span_map.rs b/crates/hir-expand/src/span_map.rs index ef86be6709..eae2c8fb63 100644 --- a/crates/hir-expand/src/span_map.rs +++ b/crates/hir-expand/src/span_map.rs @@ -1,13 +1,15 @@ //! Span maps for real files and macro expansions. -use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span}; -use syntax::{AstNode, TextRange}; + +use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId}; +use stdx::TupleExt; +use syntax::{ast, AstNode, TextRange}; use triomphe::Arc; pub use span::RealSpanMap; -use crate::db::ExpandDatabase; +use crate::{attrs::collect_attrs, db::ExpandDatabase}; -pub type ExpansionSpanMap = span::SpanMap; +pub type ExpansionSpanMap = span::SpanMap; /// Spanmap for a macro file or a real file #[derive(Clone, Debug, PartialEq, Eq)] @@ -82,13 +84,54 @@ pub(crate) fn real_span_map(db: &dyn ExpandDatabase, file_id: FileId) -> Arc + { + if let Some(extern_item_list) = it.extern_item_list() { + pairs.extend( + extern_item_list.extern_items().map(ast::Item::from).map(item_to_entry), + ); + } + } + ast::Item::Impl(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + if let Some(assoc_item_list) = it.assoc_item_list() { + pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry)); + } + } + ast::Item::Module(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + if let Some(item_list) = it.item_list() { + pairs.extend(item_list.items().map(item_to_entry)); + } + } + ast::Item::Trait(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => { + if let Some(assoc_item_list) = it.assoc_item_list() { + pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry)); + } + } + _ => (), + }); Arc::new(RealSpanMap::from_file( file_id, diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index 41e2f7ad73..3cfedcdcb4 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -23,10 +23,10 @@ oorandom = "11.1.3" tracing.workspace = true rustc-hash.workspace = true scoped-tls = "1.0.0" -chalk-solve = { version = "0.96.0", default-features = false } -chalk-ir = "0.96.0" -chalk-recursive = { version = "0.96.0", default-features = false } -chalk-derive = "0.96.0" +chalk-solve.workspace = true +chalk-ir.workspace = true +chalk-recursive.workspace = true +chalk-derive.workspace = true la-arena.workspace = true once_cell = "1.17.0" triomphe.workspace = true diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs index 8d819e41aa..e2446c3425 100644 --- a/crates/hir-ty/src/autoderef.rs +++ b/crates/hir-ty/src/autoderef.rs @@ -113,7 +113,7 @@ pub(crate) fn autoderef_step( ty: Ty, explicit: bool, ) -> Option<(AutoderefKind, Ty)> { - if let Some(derefed) = builtin_deref(table, &ty, explicit) { + if let Some(derefed) = builtin_deref(table.db, &ty, explicit) { Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed))) } else { Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?)) @@ -121,7 +121,7 @@ pub(crate) fn autoderef_step( } pub(crate) fn builtin_deref<'ty>( - table: &mut InferenceTable<'_>, + db: &dyn HirDatabase, ty: &'ty Ty, explicit: bool, ) -> Option<&'ty Ty> { @@ -129,7 +129,7 @@ pub(crate) fn builtin_deref<'ty>( TyKind::Ref(.., ty) => Some(ty), TyKind::Raw(.., ty) if explicit => Some(ty), &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => { - if crate::lang_items::is_box(table.db, adt) { + if crate::lang_items::is_box(db, adt) { substs.at(Interner, 0).ty(Interner) } else { None diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 9cea414e1a..34ba17f145 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -22,7 +22,7 @@ mod pat; mod path; pub(crate) mod unify; -use std::{convert::identity, ops::Index}; +use std::{convert::identity, iter, ops::Index}; use chalk_ir::{ cast::Cast, fold::TypeFoldable, interner::HasInterner, DebruijnIndex, Mutability, Safety, @@ -777,7 +777,15 @@ impl<'a> InferenceContext<'a> { param_tys.push(va_list_ty) } - for (ty, pat) in param_tys.into_iter().zip(self.body.params.iter()) { + let mut param_tys = param_tys.into_iter().chain(iter::repeat(self.table.new_type_var())); + if let Some(self_param) = self.body.self_param { + if let Some(ty) = param_tys.next() { + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + self.write_binding_ty(self_param, ty); + } + } + for (ty, pat) in param_tys.zip(&*self.body.params) { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 61638c43d9..ff6de61ba6 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -647,7 +647,7 @@ impl InferenceTable<'_> { let goal: InEnvironment = InEnvironment::new(&self.trait_env.env, coerce_unsized_tref.cast(Interner)); - let canonicalized = self.canonicalize(goal); + let canonicalized = self.canonicalize_with_free_vars(goal); // FIXME: rustc's coerce_unsized is more specialized -- it only tries to // solve `CoerceUnsized` and `Unsize` goals at this point and leaves the diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index c377a51e7d..a3dab1fd9d 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -312,15 +312,13 @@ impl InferenceContext<'_> { Expr::Call { callee, args, .. } => { let callee_ty = self.infer_expr(*callee, &Expectation::none()); let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false); - let (res, derefed_callee) = 'b: { - // manual loop to be able to access `derefs.table` - while let Some((callee_deref_ty, _)) = derefs.next() { - let res = derefs.table.callable_sig(&callee_deref_ty, args.len()); - if res.is_some() { - break 'b (res, callee_deref_ty); - } + let (res, derefed_callee) = loop { + let Some((callee_deref_ty, _)) = derefs.next() else { + break (None, callee_ty.clone()); + }; + if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) { + break (Some(res), callee_deref_ty); } - (None, callee_ty.clone()) }; // if the function is unresolved, we use is_varargs=true to // suppress the arg count diagnostic here @@ -657,7 +655,7 @@ impl InferenceContext<'_> { ); } } - if let Some(derefed) = builtin_deref(&mut self.table, &inner_ty, true) { + if let Some(derefed) = builtin_deref(self.table.db, &inner_ty, true) { self.resolve_ty_shallow(derefed) } else { deref_by_trait(&mut self.table, inner_ty) @@ -774,7 +772,7 @@ impl InferenceContext<'_> { let receiver_adjustments = method_resolution::resolve_indexing_op( self.db, self.table.trait_env.clone(), - canonicalized.value, + canonicalized, index_trait, ); let (self_ty, mut adj) = receiver_adjustments @@ -1559,7 +1557,7 @@ impl InferenceContext<'_> { let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); let resolved = method_resolution::lookup_method( self.db, - &canonicalized_receiver.value, + &canonicalized_receiver, self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), @@ -1608,7 +1606,7 @@ impl InferenceContext<'_> { let resolved = method_resolution::lookup_method( self.db, - &canonicalized_receiver.value, + &canonicalized_receiver, self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), @@ -1641,7 +1639,7 @@ impl InferenceContext<'_> { }; let assoc_func_with_same_name = method_resolution::iterate_method_candidates( - &canonicalized_receiver.value, + &canonicalized_receiver, self.db, self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 16ae028427..8f537bb448 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -321,7 +321,7 @@ impl InferenceContext<'_> { let mut not_visible = None; let res = method_resolution::iterate_method_candidates( - &canonical_ty.value, + &canonical_ty, self.db, self.table.trait_env.clone(), self.get_traits_in_scope().as_ref().left_or_else(|&it| it), diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 1d0150d850..be7547f9ba 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -23,12 +23,9 @@ use crate::{ }; impl InferenceContext<'_> { - pub(super) fn canonicalize + HasInterner>( - &mut self, - t: T, - ) -> Canonicalized + pub(super) fn canonicalize(&mut self, t: T) -> Canonical where - T: HasInterner, + T: TypeFoldable + HasInterner, { self.table.canonicalize(t) } @@ -128,14 +125,14 @@ impl> Canonicalized { }), ); for (i, v) in solution.value.iter(Interner).enumerate() { - let var = self.free_vars[i].clone(); + let var = &self.free_vars[i]; if let Some(ty) = v.ty(Interner) { // eagerly replace projections in the type; we may be getting types // e.g. from where clauses where this hasn't happened yet let ty = ctx.normalize_associated_types_in(new_vars.apply(ty.clone(), Interner)); ctx.unify(var.assert_ty_ref(Interner), &ty); } else { - let _ = ctx.try_unify(&var, &new_vars.apply(v.clone(), Interner)); + let _ = ctx.try_unify(var, &new_vars.apply(v.clone(), Interner)); } } } @@ -243,7 +240,7 @@ pub(crate) struct InferenceTable<'a> { pub(crate) db: &'a dyn HirDatabase, pub(crate) trait_env: Arc, var_unification_table: ChalkInferenceTable, - type_variable_table: Vec, + type_variable_table: SmallVec<[TypeVariableFlags; 16]>, pending_obligations: Vec>>, /// Double buffer used in [`Self::resolve_obligations_as_possible`] to cut down on /// temporary allocations. @@ -252,8 +249,8 @@ pub(crate) struct InferenceTable<'a> { pub(crate) struct InferenceTableSnapshot { var_table_snapshot: chalk_solve::infer::InferenceSnapshot, + type_variable_table: SmallVec<[TypeVariableFlags; 16]>, pending_obligations: Vec>>, - type_variable_table_snapshot: Vec, } impl<'a> InferenceTable<'a> { @@ -262,7 +259,7 @@ impl<'a> InferenceTable<'a> { db, trait_env, var_unification_table: ChalkInferenceTable::new(), - type_variable_table: Vec::new(), + type_variable_table: SmallVec::new(), pending_obligations: Vec::new(), resolve_obligations_buffer: Vec::new(), } @@ -292,14 +289,14 @@ impl<'a> InferenceTable<'a> { } fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty { + let is_diverging = self + .type_variable_table + .get(iv.index() as usize) + .map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING)); + if is_diverging { + return TyKind::Never.intern(Interner); + } match kind { - _ if self - .type_variable_table - .get(iv.index() as usize) - .map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING)) => - { - TyKind::Never - } TyVariableKind::General => TyKind::Error, TyVariableKind::Integer => TyKind::Scalar(Scalar::Int(IntTy::I32)), TyVariableKind::Float => TyKind::Scalar(Scalar::Float(FloatTy::F64)), @@ -307,12 +304,9 @@ impl<'a> InferenceTable<'a> { .intern(Interner) } - pub(crate) fn canonicalize + HasInterner>( - &mut self, - t: T, - ) -> Canonicalized + pub(crate) fn canonicalize_with_free_vars(&mut self, t: T) -> Canonicalized where - T: HasInterner, + T: TypeFoldable + HasInterner, { // try to resolve obligations before canonicalizing, since this might // result in new knowledge about variables @@ -326,6 +320,16 @@ impl<'a> InferenceTable<'a> { Canonicalized { value: result.quantified, free_vars } } + pub(crate) fn canonicalize(&mut self, t: T) -> Canonical + where + T: TypeFoldable + HasInterner, + { + // try to resolve obligations before canonicalizing, since this might + // result in new knowledge about variables + self.resolve_obligations_as_possible(); + self.var_unification_table.canonicalize(Interner, t).quantified + } + /// Recurses through the given type, normalizing associated types mentioned /// in it by replacing them by type variables and registering obligations to /// resolve later. This should be done once for every type we get from some @@ -541,7 +545,7 @@ impl<'a> InferenceTable<'a> { Err(_) => return false, }; result.goals.iter().all(|goal| { - let canonicalized = self.canonicalize(goal.clone()); + let canonicalized = self.canonicalize_with_free_vars(goal.clone()); self.try_resolve_obligation(&canonicalized).is_some() }) } @@ -575,19 +579,15 @@ impl<'a> InferenceTable<'a> { pub(crate) fn snapshot(&mut self) -> InferenceTableSnapshot { let var_table_snapshot = self.var_unification_table.snapshot(); - let type_variable_table_snapshot = self.type_variable_table.clone(); + let type_variable_table = self.type_variable_table.clone(); let pending_obligations = self.pending_obligations.clone(); - InferenceTableSnapshot { - var_table_snapshot, - pending_obligations, - type_variable_table_snapshot, - } + InferenceTableSnapshot { var_table_snapshot, pending_obligations, type_variable_table } } #[tracing::instrument(skip_all)] pub(crate) fn rollback_to(&mut self, snapshot: InferenceTableSnapshot) { self.var_unification_table.rollback_to(snapshot.var_table_snapshot); - self.type_variable_table = snapshot.type_variable_table_snapshot; + self.type_variable_table = snapshot.type_variable_table; self.pending_obligations = snapshot.pending_obligations; } @@ -606,7 +606,7 @@ impl<'a> InferenceTable<'a> { let in_env = InEnvironment::new(&self.trait_env.env, goal); let canonicalized = self.canonicalize(in_env); - self.db.trait_solve(self.trait_env.krate, self.trait_env.block, canonicalized.value) + self.db.trait_solve(self.trait_env.krate, self.trait_env.block, canonicalized) } pub(crate) fn register_obligation(&mut self, goal: Goal) { @@ -615,7 +615,7 @@ impl<'a> InferenceTable<'a> { } fn register_obligation_in_env(&mut self, goal: InEnvironment) { - let canonicalized = self.canonicalize(goal); + let canonicalized = self.canonicalize_with_free_vars(goal); let solution = self.try_resolve_obligation(&canonicalized); if matches!(solution, Some(Solution::Ambig(_))) { self.pending_obligations.push(canonicalized); @@ -798,7 +798,7 @@ impl<'a> InferenceTable<'a> { let trait_data = self.db.trait_data(fn_once_trait); let output_assoc_type = trait_data.associated_type_by_name(&name![Output])?; - let mut arg_tys = vec![]; + let mut arg_tys = Vec::with_capacity(num_args); let arg_ty = TyBuilder::tuple(num_args) .fill(|it| { let arg = match it { @@ -828,11 +828,7 @@ impl<'a> InferenceTable<'a> { environment: trait_env.clone(), }; let canonical = self.canonicalize(obligation.clone()); - if self - .db - .trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner)) - .is_some() - { + if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() { self.register_obligation(obligation.goal); let return_ty = self.normalize_projection_ty(projection); for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] { @@ -845,7 +841,7 @@ impl<'a> InferenceTable<'a> { let canonical = self.canonicalize(obligation.clone()); if self .db - .trait_solve(krate, self.trait_env.block, canonical.value.cast(Interner)) + .trait_solve(krate, self.trait_env.block, canonical.cast(Interner)) .is_some() { return Some((fn_x, arg_tys, return_ty)); diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index dea292711d..9655981cc9 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -371,8 +371,8 @@ pub fn layout_of_ty_query( TyKind::Never => cx.layout_of_never_type(), TyKind::Dyn(_) | TyKind::Foreign(_) => { let mut unit = layout_of_unit(&cx, dl)?; - match unit.abi { - Abi::Aggregate { ref mut sized } => *sized = false, + match &mut unit.abi { + Abi::Aggregate { sized } => *sized = false, _ => return Err(LayoutError::Unknown), } unit diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index e68dbe7b02..a679a114b4 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -213,7 +213,7 @@ impl TraitImpls { // To better support custom derives, collect impls in all unnamed const items. // const _: () = { ... }; - for konst in module_data.scope.unnamed_consts(db.upcast()) { + for konst in module_data.scope.unnamed_consts() { let body = db.body(konst.into()); for (_, block_def_map) in body.blocks(db.upcast()) { Self::collect_def_map(db, map, &block_def_map); @@ -337,7 +337,7 @@ impl InherentImpls { // To better support custom derives, collect impls in all unnamed const items. // const _: () = { ... }; - for konst in module_data.scope.unnamed_consts(db.upcast()) { + for konst in module_data.scope.unnamed_consts() { let body = db.body(konst.into()); for (_, block_def_map) in body.blocks(db.upcast()) { self.collect_def_map(db, &block_def_map); @@ -972,10 +972,9 @@ pub fn iterate_method_candidates_dyn( deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| { iterate_method_candidates_with_autoref( - &receiver_ty, + &mut table, + receiver_ty, adj, - db, - env.clone(), traits_in_scope, visible_from_module, name, @@ -1000,10 +999,9 @@ pub fn iterate_method_candidates_dyn( #[tracing::instrument(skip_all, fields(name = ?name))] fn iterate_method_candidates_with_autoref( - receiver_ty: &Canonical, + table: &mut InferenceTable<'_>, + receiver_ty: Canonical, first_adjustment: ReceiverAdjustments, - db: &dyn HirDatabase, - env: Arc, traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, @@ -1016,10 +1014,9 @@ fn iterate_method_candidates_with_autoref( let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| { iterate_method_candidates_by_receiver( + table, receiver_ty, first_adjustment, - db, - env.clone(), traits_in_scope, visible_from_module, name, @@ -1034,7 +1031,7 @@ fn iterate_method_candidates_with_autoref( maybe_reborrowed.autoderefs += 1; } - iterate_method_candidates_by_receiver(receiver_ty, maybe_reborrowed)?; + iterate_method_candidates_by_receiver(receiver_ty.clone(), maybe_reborrowed)?; let refed = Canonical { value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone()) @@ -1042,7 +1039,7 @@ fn iterate_method_candidates_with_autoref( binders: receiver_ty.binders.clone(), }; - iterate_method_candidates_by_receiver(&refed, first_adjustment.with_autoref(Mutability::Not))?; + iterate_method_candidates_by_receiver(refed, first_adjustment.with_autoref(Mutability::Not))?; let ref_muted = Canonical { value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone()) @@ -1050,58 +1047,53 @@ fn iterate_method_candidates_with_autoref( binders: receiver_ty.binders.clone(), }; - iterate_method_candidates_by_receiver( - &ref_muted, - first_adjustment.with_autoref(Mutability::Mut), - ) + iterate_method_candidates_by_receiver(ref_muted, first_adjustment.with_autoref(Mutability::Mut)) } #[tracing::instrument(skip_all, fields(name = ?name))] fn iterate_method_candidates_by_receiver( - receiver_ty: &Canonical, + table: &mut InferenceTable<'_>, + receiver_ty: Canonical, receiver_adjustments: ReceiverAdjustments, - db: &dyn HirDatabase, - env: Arc, traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, ) -> ControlFlow<()> { - let mut table = InferenceTable::new(db, env); let receiver_ty = table.instantiate_canonical(receiver_ty.clone()); - let snapshot = table.snapshot(); // We're looking for methods with *receiver* type receiver_ty. These could // be found in any of the derefs of receiver_ty, so we have to go through // that, including raw derefs. - let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true); - while let Some((self_ty, _)) = autoderef.next() { - iterate_inherent_methods( - &self_ty, - autoderef.table, - name, - Some(&receiver_ty), - Some(receiver_adjustments.clone()), - visible_from_module, - &mut callback, - )? - } - - table.rollback_to(snapshot); - - let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true); - while let Some((self_ty, _)) = autoderef.next() { - iterate_trait_method_candidates( - &self_ty, - autoderef.table, - traits_in_scope, - name, - Some(&receiver_ty), - Some(receiver_adjustments.clone()), - &mut callback, - )? - } - - ControlFlow::Continue(()) + table.run_in_snapshot(|table| { + let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true); + while let Some((self_ty, _)) = autoderef.next() { + iterate_inherent_methods( + &self_ty, + autoderef.table, + name, + Some(&receiver_ty), + Some(receiver_adjustments.clone()), + visible_from_module, + &mut callback, + )? + } + ControlFlow::Continue(()) + })?; + table.run_in_snapshot(|table| { + let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true); + while let Some((self_ty, _)) = autoderef.next() { + iterate_trait_method_candidates( + &self_ty, + autoderef.table, + traits_in_scope, + name, + Some(&receiver_ty), + Some(receiver_adjustments.clone()), + &mut callback, + )? + } + ControlFlow::Continue(()) + }) } #[tracing::instrument(skip_all, fields(name = ?name))] @@ -1147,9 +1139,9 @@ fn iterate_trait_method_candidates( callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, ) -> ControlFlow<()> { let db = table.db; - let env = table.trait_env.clone(); - let canonical_self_ty = table.canonicalize(self_ty.clone()).value; + let canonical_self_ty = table.canonicalize(self_ty.clone()); + let TraitEnvironment { krate, block, .. } = *table.trait_env; 'traits: for &t in traits_in_scope { let data = db.trait_data(t); @@ -1164,7 +1156,7 @@ fn iterate_trait_method_candidates( { // FIXME: this should really be using the edition of the method name's span, in case it // comes from a macro - if db.crate_graph()[env.krate].edition < Edition::Edition2021 { + if db.crate_graph()[krate].edition < Edition::Edition2021 { continue; } } @@ -1183,8 +1175,8 @@ fn iterate_trait_method_candidates( IsValidCandidate::No => continue, }; if !known_implemented { - let goal = generic_implements_goal(db, env.clone(), t, &canonical_self_ty); - if db.trait_solve(env.krate, env.block, goal.cast(Interner)).is_none() { + let goal = generic_implements_goal(db, &table.trait_env, t, &canonical_self_ty); + if db.trait_solve(krate, block, goal.cast(Interner)).is_none() { continue 'traits; } } @@ -1365,7 +1357,7 @@ pub(crate) fn resolve_indexing_op( let ty = table.instantiate_canonical(ty); let deref_chain = autoderef_method_receiver(&mut table, ty); for (ty, adj) in deref_chain { - let goal = generic_implements_goal(db, table.trait_env.clone(), index_trait, &ty); + let goal = generic_implements_goal(db, &table.trait_env, index_trait, &ty); if db .trait_solve(table.trait_env.krate, table.trait_env.block, goal.cast(Interner)) .is_some() @@ -1548,7 +1540,7 @@ fn is_valid_impl_fn_candidate( for goal in goals.clone() { let in_env = InEnvironment::new(&table.trait_env.env, goal); - let canonicalized = table.canonicalize(in_env); + let canonicalized = table.canonicalize_with_free_vars(in_env); let solution = table.db.trait_solve( table.trait_env.krate, table.trait_env.block, @@ -1586,10 +1578,10 @@ fn is_valid_impl_fn_candidate( pub fn implements_trait( ty: &Canonical, db: &dyn HirDatabase, - env: Arc, + env: &TraitEnvironment, trait_: TraitId, ) -> bool { - let goal = generic_implements_goal(db, env.clone(), trait_, ty); + let goal = generic_implements_goal(db, env, trait_, ty); let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner)); solution.is_some() @@ -1598,10 +1590,10 @@ pub fn implements_trait( pub fn implements_trait_unique( ty: &Canonical, db: &dyn HirDatabase, - env: Arc, + env: &TraitEnvironment, trait_: TraitId, ) -> bool { - let goal = generic_implements_goal(db, env.clone(), trait_, ty); + let goal = generic_implements_goal(db, env, trait_, ty); let solution = db.trait_solve(env.krate, env.block, goal.cast(Interner)); matches!(solution, Some(crate::Solution::Unique(_))) @@ -1612,32 +1604,34 @@ pub fn implements_trait_unique( #[tracing::instrument(skip_all)] fn generic_implements_goal( db: &dyn HirDatabase, - env: Arc, + env: &TraitEnvironment, trait_: TraitId, self_ty: &Canonical, ) -> Canonical> { - let mut kinds = self_ty.binders.interned().to_vec(); + let binders = self_ty.binders.interned(); let trait_ref = TyBuilder::trait_ref(db, trait_) .push(self_ty.value.clone()) - .fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len()) + .fill_with_bound_vars(DebruijnIndex::INNERMOST, binders.len()) .build(); - kinds.extend(trait_ref.substitution.iter(Interner).skip(1).map(|it| { - let vk = match it.data(Interner) { - chalk_ir::GenericArgData::Ty(_) => { - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) - } - chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime, - chalk_ir::GenericArgData::Const(c) => { - chalk_ir::VariableKind::Const(c.data(Interner).ty.clone()) - } - }; - chalk_ir::WithKind::new(vk, UniverseIndex::ROOT) - })); + + let kinds = + binders.iter().cloned().chain(trait_ref.substitution.iter(Interner).skip(1).map(|it| { + let vk = match it.data(Interner) { + chalk_ir::GenericArgData::Ty(_) => { + chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) + } + chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime, + chalk_ir::GenericArgData::Const(c) => { + chalk_ir::VariableKind::Const(c.data(Interner).ty.clone()) + } + }; + chalk_ir::WithKind::new(vk, UniverseIndex::ROOT) + })); + let binders = CanonicalVarKinds::from_iter(Interner, kinds); + let obligation = trait_ref.cast(Interner); - Canonical { - binders: CanonicalVarKinds::from_iter(Interner, kinds), - value: InEnvironment::new(&env.env, obligation), - } + let value = InEnvironment::new(&env.env, obligation); + Canonical { binders, value } } fn autoderef_method_receiver( @@ -1648,7 +1642,7 @@ fn autoderef_method_receiver( let mut autoderef = autoderef::Autoderef::new(table, ty, false); while let Some((ty, derefs)) = autoderef.next() { deref_chain.push(( - autoderef.table.canonicalize(ty).value, + autoderef.table.canonicalize(ty), ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false }, )); } diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index cfaef2a392..d513355037 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -1165,6 +1165,7 @@ impl MirBody { pub enum MirSpan { ExprId(ExprId), PatId(PatId), + SelfParam, Unknown, } diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 2428678d72..fd98141af6 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -376,6 +376,10 @@ impl MirEvalError { Ok(s) => s.map(|it| it.syntax_node_ptr()), Err(_) => continue, }, + MirSpan::SelfParam => match source_map.self_param_syntax() { + Some(s) => s.map(|it| it.syntax_node_ptr()), + None => continue, + }, MirSpan::Unknown => continue, }; let file_id = span.file_id.original_file(db.upcast()); diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index d0f739e6ac..7e582c03ef 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1810,9 +1810,20 @@ impl<'ctx> MirLowerCtx<'ctx> { fn lower_params_and_bindings( &mut self, params: impl Iterator + Clone, + self_binding: Option<(BindingId, Ty)>, pick_binding: impl Fn(BindingId) -> bool, ) -> Result { let base_param_count = self.result.param_locals.len(); + let self_binding = match self_binding { + Some((self_binding, ty)) => { + let local_id = self.result.locals.alloc(Local { ty }); + self.drop_scopes.last_mut().unwrap().locals.push(local_id); + self.result.binding_locals.insert(self_binding, local_id); + self.result.param_locals.push(local_id); + Some(self_binding) + } + None => None, + }; self.result.param_locals.extend(params.clone().map(|(it, ty)| { let local_id = self.result.locals.alloc(Local { ty }); self.drop_scopes.last_mut().unwrap().locals.push(local_id); @@ -1838,9 +1849,23 @@ impl<'ctx> MirLowerCtx<'ctx> { } } let mut current = self.result.start_block; - for ((param, _), local) in - params.zip(self.result.param_locals.clone().into_iter().skip(base_param_count)) - { + if let Some(self_binding) = self_binding { + let local = self.result.param_locals.clone()[base_param_count]; + if local != self.binding_local(self_binding)? { + let r = self.match_self_param(self_binding, current, local)?; + if let Some(b) = r.1 { + self.set_terminator(b, TerminatorKind::Unreachable, MirSpan::SelfParam); + } + current = r.0; + } + } + let local_params = self + .result + .param_locals + .clone() + .into_iter() + .skip(base_param_count + self_binding.is_some() as usize); + for ((param, _), local) in params.zip(local_params) { if let Pat::Bind { id, .. } = self.body[param] { if local == self.binding_local(id)? { continue; @@ -2019,6 +2044,7 @@ pub fn mir_body_for_closure_query( }; let current = ctx.lower_params_and_bindings( args.iter().zip(sig.params().iter()).map(|(it, y)| (*it, y.clone())), + None, |_| true, )?; if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? { @@ -2149,16 +2175,16 @@ pub fn lower_to_mir( let substs = TyBuilder::placeholder_subst(db, fid); let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs); + let mut params = callable_sig.params().iter(); + let self_param = body.self_param.and_then(|id| Some((id, params.next()?.clone()))); break 'b ctx.lower_params_and_bindings( - body.params - .iter() - .zip(callable_sig.params().iter()) - .map(|(it, y)| (*it, y.clone())), + body.params.iter().zip(params).map(|(it, y)| (*it, y.clone())), + self_param, binding_picker, )?; } } - ctx.lower_params_and_bindings([].into_iter(), binding_picker)? + ctx.lower_params_and_bindings([].into_iter(), None, binding_picker)? }; if let Some(current) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { let current = ctx.pop_drop_scope_assert_finished(current, root_expr.into())?; diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 90cbd13a6c..7596906794 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -11,7 +11,7 @@ use crate::{ Substitution, SwitchTargets, TerminatorKind, TupleFieldId, TupleId, TyBuilder, TyKind, ValueNs, VariantData, VariantId, }, - MutBorrowKind, + LocalId, MutBorrowKind, }, BindingMode, }; @@ -82,6 +82,22 @@ impl MirLowerCtx<'_> { Ok((current, current_else)) } + pub(super) fn match_self_param( + &mut self, + id: BindingId, + current: BasicBlockId, + local: LocalId, + ) -> Result<(BasicBlockId, Option)> { + self.pattern_match_binding( + id, + BindingMode::Move, + local.into(), + MirSpan::SelfParam, + current, + None, + ) + } + fn pattern_match_inner( &mut self, mut current: BasicBlockId, @@ -283,9 +299,9 @@ impl MirLowerCtx<'_> { (current, current_else) = self.pattern_match_inner(current, current_else, next_place, pat, mode)?; } - if let Some(slice) = slice { + if let &Some(slice) = slice { if mode == MatchingMode::Bind { - if let Pat::Bind { id, subpat: _ } = self.body[*slice] { + if let Pat::Bind { id, subpat: _ } = self.body[slice] { let next_place = cond_place.project( ProjectionElem::Subslice { from: prefix.len() as u64, @@ -293,11 +309,12 @@ impl MirLowerCtx<'_> { }, &mut self.result.projection_store, ); + let mode = self.infer.binding_modes[slice]; (current, current_else) = self.pattern_match_binding( id, - *slice, + mode, next_place, - (*slice).into(), + (slice).into(), current, current_else, )?; @@ -398,9 +415,10 @@ impl MirLowerCtx<'_> { self.pattern_match_inner(current, current_else, cond_place, *subpat, mode)? } if mode == MatchingMode::Bind { + let mode = self.infer.binding_modes[pattern]; self.pattern_match_binding( *id, - pattern, + mode, cond_place, pattern.into(), current, @@ -437,14 +455,13 @@ impl MirLowerCtx<'_> { fn pattern_match_binding( &mut self, id: BindingId, - pat: PatId, + mode: BindingMode, cond_place: Place, span: MirSpan, current: BasicBlockId, current_else: Option, ) -> Result<(BasicBlockId, Option)> { let target_place = self.binding_local(id)?; - let mode = self.infer.binding_modes[pat]; self.push_storage_live(id, current)?; self.push_assignment( current, diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 5e159236f4..d699067b5a 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -12,7 +12,7 @@ mod traits; use std::env; -use base_db::{FileRange, SourceDatabaseExt}; +use base_db::{FileRange, SourceDatabaseExt2 as _}; use expect_test::Expect; use hir_def::{ body::{Body, BodySourceMap, SyntheticSyntax}, @@ -164,7 +164,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour Some(value) => value, None => continue, }; - let range = node.as_ref().original_file_range(&db); + let range = node.as_ref().original_file_range_rooted(&db); if let Some(expected) = types.remove(&range) { let actual = if display_source { ty.display_source_code(&db, def.module(&db), true).unwrap() @@ -180,7 +180,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour Some(value) => value, None => continue, }; - let range = node.as_ref().original_file_range(&db); + let range = node.as_ref().original_file_range_rooted(&db); if let Some(expected) = types.remove(&range) { let actual = if display_source { ty.display_source_code(&db, def.module(&db), true).unwrap() @@ -211,7 +211,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour }) else { continue; }; - let range = node.as_ref().original_file_range(&db); + let range = node.as_ref().original_file_range_rooted(&db); let actual = format!( "expected {}, got {}", mismatch.expected.display_test(&db), @@ -293,20 +293,29 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { let mut types: Vec<(InFile, &Ty)> = Vec::new(); let mut mismatches: Vec<(InFile, &TypeMismatch)> = Vec::new(); + if let Some(self_param) = body.self_param { + let ty = &inference_result.type_of_binding[self_param]; + if let Some(syntax_ptr) = body_source_map.self_param_syntax() { + let root = db.parse_or_expand(syntax_ptr.file_id); + let node = syntax_ptr.map(|ptr| ptr.to_node(&root).syntax().clone()); + types.push((node.clone(), ty)); + } + } + for (pat, mut ty) in inference_result.type_of_pat.iter() { if let Pat::Bind { id, .. } = body.pats[pat] { ty = &inference_result.type_of_binding[id]; } - let syntax_ptr = match body_source_map.pat_syntax(pat) { + let node = match body_source_map.pat_syntax(pat) { Ok(sp) => { let root = db.parse_or_expand(sp.file_id); sp.map(|ptr| ptr.to_node(&root).syntax().clone()) } Err(SyntheticSyntax) => continue, }; - types.push((syntax_ptr.clone(), ty)); + types.push((node.clone(), ty)); if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat) { - mismatches.push((syntax_ptr, mismatch)); + mismatches.push((node, mismatch)); } } @@ -575,7 +584,7 @@ fn salsa_bug() { } "; - db.set_file_text(pos.file_id, Arc::from(new_text)); + db.set_file_text(pos.file_id, new_text); let module = db.module_for_file(pos.file_id); let crate_def_map = module.def_map(&db); diff --git a/crates/hir-ty/src/tests/incremental.rs b/crates/hir-ty/src/tests/incremental.rs index 82d934009f..6066ec69c9 100644 --- a/crates/hir-ty/src/tests/incremental.rs +++ b/crates/hir-ty/src/tests/incremental.rs @@ -1,6 +1,5 @@ -use base_db::SourceDatabaseExt; +use base_db::SourceDatabaseExt2 as _; use test_fixture::WithFixture; -use triomphe::Arc; use crate::{db::HirDatabase, test_db::TestDB}; @@ -33,7 +32,7 @@ fn foo() -> i32 { 1 }"; - db.set_file_text(pos.file_id, Arc::from(new_text)); + db.set_file_text(pos.file_id, new_text); { let events = db.log_executed(|| { @@ -85,7 +84,7 @@ fn baz() -> i32 { } "; - db.set_file_text(pos.file_id, Arc::from(new_text)); + db.set_file_text(pos.file_id, new_text); { let events = db.log_executed(|| { diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index c837fae3fe..8609ba4103 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1461,28 +1461,6 @@ fn f() { ); } -#[test] -fn trait_impl_in_synstructure_const() { - check_types( - r#" -struct S; - -trait Tr { - fn method(&self) -> u16; -} - -const _DERIVE_Tr_: () = { - impl Tr for S {} -}; - -fn f() { - S.method(); - //^^^^^^^^^^ u16 -} - "#, - ); -} - #[test] fn inherent_impl_in_unnamed_const() { check_types( @@ -1795,6 +1773,21 @@ fn test() { ); } +#[test] +fn deref_into_inference_var() { + check_types( + r#" +//- minicore:deref +struct A(T); +impl core::ops::Deref for A {} +impl A { fn foo(&self) {} } +fn main() { + A(0).foo(); + //^^^^^^^^^^ () +} +"#, + ); +} #[test] fn receiver_adjustment_autoref() { check( diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index ffd6a6051b..917e9f4408 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -2121,6 +2121,7 @@ async fn main() { "#, expect![[r#" 16..193 '{ ...2 }; }': () + 16..193 '{ ...2 }; }': impl Future 26..27 'x': i32 30..43 'unsafe { 92 }': i32 39..41 '92': i32 @@ -2131,6 +2132,8 @@ async fn main() { 73..75 '()': () 95..96 'z': ControlFlow<(), ()> 130..140 'try { () }': ControlFlow<(), ()> + 130..140 'try { () }': fn from_output>( as Try>::Output) -> ControlFlow<(), ()> + 130..140 'try { () }': ControlFlow<(), ()> 136..138 '()': () 150..151 'w': i32 154..166 'const { 92 }': i32 diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index fa9fe4953e..4518422d27 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -204,7 +204,7 @@ pub struct NoSuchField { #[derive(Debug)] pub struct PrivateAssocItem { - pub expr_or_pat: InFile>>>, + pub expr_or_pat: InFile>>, pub item: AssocItem, } @@ -240,7 +240,7 @@ pub struct UnresolvedMethodCall { #[derive(Debug)] pub struct UnresolvedAssocItem { - pub expr_or_pat: InFile>>>, + pub expr_or_pat: InFile>>, } #[derive(Debug)] diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index cdc0db8653..c5d44c11f2 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -159,6 +159,7 @@ impl HirDisplay for Adt { impl HirDisplay for Struct { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let module_id = self.module(f.db).id; + // FIXME: Render repr if its set explicitly? write_visibility(module_id, self.visibility(f.db), f)?; f.write_str("struct ")?; write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; @@ -166,37 +167,40 @@ impl HirDisplay for Struct { write_generic_params(def_id, f)?; let variant_data = self.variant_data(f.db); - if let StructKind::Tuple = variant_data.kind() { - f.write_char('(')?; - let mut it = variant_data.fields().iter().peekable(); + match variant_data.kind() { + StructKind::Tuple => { + f.write_char('(')?; + let mut it = variant_data.fields().iter().peekable(); - while let Some((id, _)) = it.next() { - let field = Field { parent: (*self).into(), id }; - write_visibility(module_id, field.visibility(f.db), f)?; - field.ty(f.db).hir_fmt(f)?; - if it.peek().is_some() { - f.write_str(", ")?; + while let Some((id, _)) = it.next() { + let field = Field { parent: (*self).into(), id }; + write_visibility(module_id, field.visibility(f.db), f)?; + field.ty(f.db).hir_fmt(f)?; + if it.peek().is_some() { + f.write_str(", ")?; + } + } + + f.write_char(')')?; + write_where_clause(def_id, f)?; + } + StructKind::Record => { + let has_where_clause = write_where_clause(def_id, f)?; + let fields = self.fields(f.db); + f.write_char(if !has_where_clause { ' ' } else { '\n' })?; + if fields.is_empty() { + f.write_str("{}")?; + } else { + f.write_str("{\n")?; + for field in self.fields(f.db) { + f.write_str(" ")?; + field.hir_fmt(f)?; + f.write_str(",\n")?; + } + f.write_str("}")?; } } - - f.write_str(");")?; - } - - write_where_clause(def_id, f)?; - - if let StructKind::Record = variant_data.kind() { - let fields = self.fields(f.db); - if fields.is_empty() { - f.write_str(" {}")?; - } else { - f.write_str(" {\n")?; - for field in self.fields(f.db) { - f.write_str(" ")?; - field.hir_fmt(f)?; - f.write_str(",\n")?; - } - f.write_str("}")?; - } + StructKind::Unit => _ = write_where_clause(def_id, f)?, } Ok(()) @@ -210,11 +214,12 @@ impl HirDisplay for Enum { write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id)); write_generic_params(def_id, f)?; - write_where_clause(def_id, f)?; + let has_where_clause = write_where_clause(def_id, f)?; let variants = self.variants(f.db); if !variants.is_empty() { - f.write_str(" {\n")?; + f.write_char(if !has_where_clause { ' ' } else { '\n' })?; + f.write_str("{\n")?; for variant in variants { f.write_str(" ")?; variant.hir_fmt(f)?; @@ -234,11 +239,12 @@ impl HirDisplay for Union { write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id)); write_generic_params(def_id, f)?; - write_where_clause(def_id, f)?; + let has_where_clause = write_where_clause(def_id, f)?; let fields = self.fields(f.db); if !fields.is_empty() { - f.write_str(" {\n")?; + f.write_char(if !has_where_clause { ' ' } else { '\n' })?; + f.write_str("{\n")?; for field in self.fields(f.db) { f.write_str(" ")?; field.hir_fmt(f)?; @@ -446,7 +452,10 @@ fn write_generic_params( Ok(()) } -fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { +fn write_where_clause( + def: GenericDefId, + f: &mut HirFormatter<'_>, +) -> Result { let params = f.db.generic_params(def); // unnamed type targets are displayed inline with the argument itself, e.g. `f: impl Y`. @@ -465,7 +474,7 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), }); if !has_displayable_predicate { - return Ok(()); + return Ok(false); } let write_target = |target: &WherePredicateTypeTarget, f: &mut HirFormatter<'_>| match target { @@ -543,7 +552,7 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), // End of final predicate. There must be at least one predicate here. f.write_char(',')?; - Ok(()) + Ok(true) } impl HirDisplay for Const { @@ -594,19 +603,20 @@ impl HirDisplay for Trait { write!(f, "trait {}", data.name.display(f.db.upcast()))?; let def_id = GenericDefId::TraitId(self.id); write_generic_params(def_id, f)?; - write_where_clause(def_id, f)?; + let has_where_clause = write_where_clause(def_id, f)?; if let Some(limit) = f.entity_limit { let assoc_items = self.items(f.db); let count = assoc_items.len().min(limit); + f.write_char(if !has_where_clause { ' ' } else { '\n' })?; if count == 0 { if assoc_items.is_empty() { - f.write_str(" {}")?; + f.write_str("{}")?; } else { - f.write_str(" { /* … */ }")?; + f.write_str("{ /* … */ }")?; } } else { - f.write_str(" {\n")?; + f.write_str("{\n")?; for item in &assoc_items[..count] { f.write_str(" ")?; match item { @@ -651,7 +661,6 @@ impl HirDisplay for TypeAlias { write!(f, "type {}", data.name.display(f.db.upcast()))?; let def_id = GenericDefId::TypeAliasId(self.id); write_generic_params(def_id, f)?; - write_where_clause(def_id, f)?; if !data.bounds.is_empty() { f.write_str(": ")?; f.write_joined(data.bounds.iter(), " + ")?; @@ -660,6 +669,7 @@ impl HirDisplay for TypeAlias { f.write_str(" = ")?; ty.hir_fmt(f)?; } + write_where_clause(def_id, f)?; Ok(()) } } diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index d10884517f..7cdcdd76d1 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -9,6 +9,7 @@ use hir_def::{ }; use hir_expand::{HirFileId, InFile}; use syntax::ast; +use tt::TextRange; use crate::{ db::HirDatabase, Adt, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl, @@ -37,6 +38,12 @@ impl Module { def_map[self.id.local_id].definition_source(db.upcast()) } + /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. + pub fn definition_source_range(self, db: &dyn HirDatabase) -> InFile { + let def_map = self.id.def_map(db.upcast()); + def_map[self.id.local_id].definition_source_range(db.upcast()) + } + pub fn definition_source_file_id(self, db: &dyn HirDatabase) -> HirFileId { let def_map = self.id.def_map(db.upcast()); def_map[self.id.local_id].definition_source_file_id() @@ -71,6 +78,13 @@ impl Module { let def_map = self.id.def_map(db.upcast()); def_map[self.id.local_id].declaration_source(db.upcast()) } + + /// Returns a text range which declares this module, either a `mod foo;` or a `mod foo {}`. + /// `None` for the crate root. + pub fn declaration_source_range(self, db: &dyn HirDatabase) -> Option> { + let def_map = self.id.def_map(db.upcast()); + def_map[self.id.local_id].declaration_source_range(db.upcast()) + } } impl HasSource for Field { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 5eed7ecd5b..b922aa8e46 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -56,8 +56,8 @@ use hir_def::{ AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander, - MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId, - TypeOrConstParamId, TypeParamId, UnionId, + ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId, TypeOrConstParamId, + TypeParamId, UnionId, }; use hir_expand::{attrs::collect_attrs, name::name, proc_macro::ProcMacroKind, MacroCallKind}; use hir_ty::{ @@ -122,7 +122,7 @@ pub use { visibility::Visibility, // FIXME: This is here since some queries take it as input that are used // outside of hir. - {AdtId, ModuleDefId}, + {AdtId, MacroId, ModuleDefId}, }, hir_expand::{ attrs::{Attr, AttrId}, @@ -754,7 +754,7 @@ impl Module { scope .declarations() .map(ModuleDef::from) - .chain(scope.unnamed_consts(db.upcast()).map(|id| ModuleDef::Const(Const::from(id)))) + .chain(scope.unnamed_consts().map(|id| ModuleDef::Const(Const::from(id)))) .collect() } @@ -1725,6 +1725,10 @@ impl DefWithBody { Ok(s) => s.map(|it| it.into()), Err(_) => continue, }, + mir::MirSpan::SelfParam => match source_map.self_param_syntax() { + Some(s) => s.map(|it| it.into()), + None => continue, + }, mir::MirSpan::Unknown => continue, }; acc.push( @@ -1776,6 +1780,11 @@ impl DefWithBody { Ok(s) => s.map(|it| it.into()), Err(_) => continue, }, + mir::MirSpan::SelfParam => match source_map.self_param_syntax() + { + Some(s) => s.map(|it| it.into()), + None => continue, + }, mir::MirSpan::Unknown => continue, }; acc.push(NeedMut { local, span }.into()); @@ -2127,8 +2136,11 @@ impl Param { pub fn as_local(&self, db: &dyn HirDatabase) -> Option { let parent = DefWithBodyId::FunctionId(self.func.into()); let body = db.body(parent); - let pat_id = body.params[self.idx]; - if let Pat::Bind { id, .. } = &body[pat_id] { + if let Some(self_param) = body.self_param.filter(|_| self.idx == 0) { + Some(Local { parent, binding_id: self_param }) + } else if let Pat::Bind { id, .. } = + &body[body.params[self.idx - body.self_param.is_some() as usize]] + { Some(Local { parent, binding_id: *id }) } else { None @@ -2143,7 +2155,7 @@ impl Param { let InFile { file_id, value } = self.func.source(db)?; let params = value.param_list()?; if params.self_param().is_some() { - params.params().nth(self.idx.checked_sub(1)?) + params.params().nth(self.idx.checked_sub(params.self_param().is_some() as usize)?) } else { params.params().nth(self.idx) } @@ -2605,6 +2617,15 @@ impl Macro { } } + pub fn is_env_or_option_env(&self, db: &dyn HirDatabase) -> bool { + match self.id { + MacroId::Macro2Id(it) => { + matches!(it.lookup(db.upcast()).expander, MacroExpander::BuiltInEager(eager) if eager.is_env_or_option_env()) + } + MacroId::MacroRulesId(_) | MacroId::ProcMacroId(_) => false, + } + } + pub fn is_attr(&self, db: &dyn HirDatabase) -> bool { matches!(self.kind(db), MacroKind::Attr) } @@ -3134,35 +3155,59 @@ impl Local { /// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = it;` pub fn sources(self, db: &dyn HirDatabase) -> Vec { let (body, source_map) = db.body_with_source_map(self.parent); - self.sources_(db, &body, &source_map).collect() + match body.self_param.zip(source_map.self_param_syntax()) { + Some((param, source)) if param == self.binding_id => { + let root = source.file_syntax(db.upcast()); + vec![LocalSource { + local: self, + source: source.map(|ast| Either::Right(ast.to_node(&root))), + }] + } + _ => body[self.binding_id] + .definitions + .iter() + .map(|&definition| { + let src = source_map.pat_syntax(definition).unwrap(); // Hmm... + let root = src.file_syntax(db.upcast()); + LocalSource { + local: self, + source: src.map(|ast| match ast.to_node(&root) { + ast::Pat::IdentPat(it) => Either::Left(it), + _ => unreachable!("local with non ident-pattern"), + }), + } + }) + .collect(), + } } /// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = it;` pub fn primary_source(self, db: &dyn HirDatabase) -> LocalSource { let (body, source_map) = db.body_with_source_map(self.parent); - let src = self.sources_(db, &body, &source_map).next().unwrap(); - src - } - - fn sources_<'a>( - self, - db: &'a dyn HirDatabase, - body: &'a hir_def::body::Body, - source_map: &'a hir_def::body::BodySourceMap, - ) -> impl Iterator + 'a { - body[self.binding_id] - .definitions - .iter() - .map(|&definition| { - let src = source_map.pat_syntax(definition).unwrap(); // Hmm... - let root = src.file_syntax(db.upcast()); - src.map(|ast| match ast.to_node(&root) { - Either::Left(ast::Pat::IdentPat(it)) => Either::Left(it), - Either::Left(_) => unreachable!("local with non ident-pattern"), - Either::Right(it) => Either::Right(it), + match body.self_param.zip(source_map.self_param_syntax()) { + Some((param, source)) if param == self.binding_id => { + let root = source.file_syntax(db.upcast()); + LocalSource { + local: self, + source: source.map(|ast| Either::Right(ast.to_node(&root))), + } + } + _ => body[self.binding_id] + .definitions + .first() + .map(|&definition| { + let src = source_map.pat_syntax(definition).unwrap(); // Hmm... + let root = src.file_syntax(db.upcast()); + LocalSource { + local: self, + source: src.map(|ast| match ast.to_node(&root) { + ast::Pat::IdentPat(it) => Either::Left(it), + _ => unreachable!("local with non ident-pattern"), + }), + } }) - }) - .map(move |source| LocalSource { local: self, source }) + .unwrap(), + } } } @@ -4037,7 +4082,7 @@ impl Type { let canonical_ty = Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) }; - method_resolution::implements_trait(&canonical_ty, db, self.env.clone(), trait_) + method_resolution::implements_trait(&canonical_ty, db, &self.env, trait_) } /// Checks that particular type `ty` implements `std::ops::FnOnce`. @@ -4052,12 +4097,7 @@ impl Type { let canonical_ty = Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) }; - method_resolution::implements_trait_unique( - &canonical_ty, - db, - self.env.clone(), - fnonce_trait, - ) + method_resolution::implements_trait_unique(&canonical_ty, db, &self.env, fnonce_trait) } // FIXME: Find better API that also handles const generics diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 99907ea15b..9796009cb4 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -681,28 +681,29 @@ impl<'db> SemanticsImpl<'db> { .filter(|&(_, include_file_id)| include_file_id == file_id) { let macro_file = invoc.as_macro_file(); - let expansion_info = cache - .entry(macro_file) - .or_insert_with(|| macro_file.expansion_info(self.db.upcast())); + let expansion_info = cache.entry(macro_file).or_insert_with(|| { + let exp_info = macro_file.expansion_info(self.db.upcast()); + + let InMacroFile { file_id, value } = exp_info.expanded(); + self.cache(value, file_id.into()); + + exp_info + }); // Create the source analyzer for the macro call scope let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file())) else { continue; }; - { - let InMacroFile { file_id: macro_file, value } = expansion_info.expanded(); - self.cache(value, macro_file.into()); - } // get mapped token in the include! macro file - let span = span::SpanData { + let span = span::Span { range: token.text_range(), anchor: span::SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID }, ctx: SyntaxContextId::ROOT, }; let Some(InMacroFile { file_id, value: mut mapped_tokens }) = - expansion_info.map_range_down(span) + expansion_info.map_range_down_exact(span) else { continue; }; @@ -753,22 +754,20 @@ impl<'db> SemanticsImpl<'db> { let def_map = sa.resolver.def_map(); let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; - let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { - let expansion_info = cache - .entry(macro_file) - .or_insert_with(|| macro_file.expansion_info(self.db.upcast())); + let exp_info = cache.entry(macro_file).or_insert_with(|| { + let exp_info = macro_file.expansion_info(self.db.upcast()); - { - let InMacroFile { file_id, value } = expansion_info.expanded(); + let InMacroFile { file_id, value } = exp_info.expanded(); self.cache(value, file_id.into()); - } - let InMacroFile { file_id, value: mapped_tokens } = - expansion_info.map_range_down(span)?; + exp_info + }); + + let InMacroFile { file_id, value: mapped_tokens } = exp_info.map_range_down(span)?; let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect(); - // if the length changed we have found a mapping for the token + // we have found a mapping for the token if the vec is non-empty let res = mapped_tokens.is_empty().not().then_some(()); // requeue the tokens we got from mapping our current token down stack.push((HirFileId::from(file_id), mapped_tokens)); @@ -851,7 +850,13 @@ impl<'db> SemanticsImpl<'db> { // remove any other token in this macro input, all their mappings are the // same as this one tokens.retain(|t| !text_range.contains_range(t.text_range())); - process_expansion_for_token(&mut stack, file_id) + + process_expansion_for_token(&mut stack, file_id).or(file_id + .eager_arg(self.db.upcast()) + .and_then(|arg| { + // also descend into eager expansions + process_expansion_for_token(&mut stack, arg.as_macro_file()) + })) } else if let Some(meta) = ast::Meta::cast(parent) { // attribute we failed expansion for earlier, this might be a derive invocation // or derive helper attribute @@ -960,7 +965,7 @@ impl<'db> SemanticsImpl<'db> { /// macro file the node resides in. pub fn original_range(&self, node: &SyntaxNode) -> FileRange { let node = self.find_file(node); - node.original_file_range(self.db.upcast()) + node.original_file_range_rooted(self.db.upcast()) } /// Attempts to map the node out of macro expanded files returning the original file range. @@ -984,9 +989,9 @@ impl<'db> SemanticsImpl<'db> { /// Attempts to map the node out of macro expanded files. /// This only work for attribute expansions, as other ones do not have nodes as input. - pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option { + pub fn original_syntax_node_rooted(&self, node: &SyntaxNode) -> Option { let InFile { file_id, .. } = self.find_file(node); - InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map( + InFile::new(file_id, node).original_syntax_node_rooted(self.db.upcast()).map( |InRealFile { file_id, value }| { self.cache(find_root(&value), file_id.into()); value @@ -997,7 +1002,7 @@ impl<'db> SemanticsImpl<'db> { pub fn diagnostics_display_range(&self, src: InFile) -> FileRange { let root = self.parse_or_expand(src.file_id); let node = src.map(|it| it.to_node(&root)); - node.as_ref().original_file_range(self.db.upcast()) + node.as_ref().original_file_range_rooted(self.db.upcast()) } fn token_ancestors_with_macros( @@ -1236,6 +1241,11 @@ impl<'db> SemanticsImpl<'db> { sa.resolve_macro_call(self.db, macro_call) } + pub fn is_proc_macro_call(&self, macro_call: &ast::MacroCall) -> bool { + self.resolve_macro_call(macro_call) + .map_or(false, |m| matches!(m.id, MacroId::ProcMacroId(..))) + } + pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool { let sa = match self.analyze(macro_call.syntax()) { Some(it) => it, diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 4733ea5a35..d4d6f0b243 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -101,7 +101,7 @@ use hir_def::{ use hir_expand::{attrs::AttrId, name::AsName, HirFileId, HirFileIdExt, MacroCallId}; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use stdx::{impl_from, never}; +use stdx::impl_from; use syntax::{ ast::{self, HasName}, AstNode, SyntaxNode, @@ -253,14 +253,8 @@ impl SourceToDefCtx<'_, '_> { src: InFile, ) -> Option<(DefWithBodyId, BindingId)> { let container = self.find_pat_or_label_container(src.syntax())?; - let (body, source_map) = self.db.body_with_source_map(container); - let pat_id = source_map.node_self_param(src.as_ref())?; - if let crate::Pat::Bind { id, .. } = body[pat_id] { - Some((container, id)) - } else { - never!(); - None - } + let body = self.db.body(container); + Some((container, body.self_param?)) } pub(super) fn label_to_def( &mut self, diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index f87e0a3897..dc96a1b03d 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -219,11 +219,10 @@ impl SourceAnalyzer { pub(crate) fn type_of_self( &self, db: &dyn HirDatabase, - param: &ast::SelfParam, + _param: &ast::SelfParam, ) -> Option { - let src = InFile { file_id: self.file_id, value: param }; - let pat_id = self.body_source_map()?.node_self_param(src)?; - let ty = self.infer.as_ref()?[pat_id].clone(); + let binding = self.body()?.self_param?; + let ty = self.infer.as_ref()?[binding].clone(); Some(Type::new_with_resolver(db, &self.resolver, ty)) } diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index 28ac5940e6..3b88836c24 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -49,7 +49,7 @@ impl DeclarationLocation { return FileRange { file_id, range: self.ptr.text_range() }; } let node = resolve_node(db, self.hir_file_id, &self.ptr); - node.as_ref().original_file_range(db.upcast()) + node.as_ref().original_file_range_rooted(db.upcast()) } } @@ -165,7 +165,6 @@ impl<'a> SymbolCollector<'a> { // Record renamed imports. // FIXME: In case it imports multiple items under different namespaces we just pick one arbitrarily // for now. - // FIXME: This parses! for id in scope.imports() { let source = id.import.child_source(self.db.upcast()); let Some(use_tree_src) = source.value.get(id.idx) else { continue }; @@ -196,7 +195,7 @@ impl<'a> SymbolCollector<'a> { }); } - for const_id in scope.unnamed_consts(self.db.upcast()) { + for const_id in scope.unnamed_consts() { self.collect_from_body(const_id); } diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs index af834c8a53..42f935651c 100644 --- a/crates/ide-assists/src/handlers/extract_module.rs +++ b/crates/ide-assists/src/handlers/extract_module.rs @@ -1,5 +1,6 @@ use std::iter; +use either::Either; use hir::{HasSource, HirFileIdExt, ModuleSource}; use ide_db::{ assists::{AssistId, AssistKind}, @@ -10,17 +11,16 @@ use ide_db::{ }; use itertools::Itertools; use smallvec::SmallVec; -use stdx::format_to; use syntax::{ algo::find_node_at_range, ast::{ self, edit::{AstNodeEdit, IndentLevel}, - make, HasName, HasVisibility, + make, HasVisibility, }, - match_ast, ted, AstNode, SourceFile, + match_ast, ted, AstNode, SyntaxKind::{self, WHITESPACE}, - SyntaxNode, TextRange, + SyntaxNode, TextRange, TextSize, }; use crate::{AssistContext, Assists}; @@ -109,76 +109,35 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti //We are getting item usages and record_fields together, record_fields //for change_visibility and usages for first point mentioned above in the process - let (usages_to_be_processed, record_fields) = module.get_usages_and_record_fields(ctx); + + let (usages_to_be_processed, record_fields, use_stmts_to_be_inserted) = + module.get_usages_and_record_fields(ctx); + + builder.edit_file(ctx.file_id()); + use_stmts_to_be_inserted.into_iter().for_each(|(_, use_stmt)| { + builder.insert(ctx.selection_trimmed().end(), format!("\n{use_stmt}")); + }); let import_paths_to_be_removed = module.resolve_imports(curr_parent_module, ctx); module.change_visibility(record_fields); - let mut body_items: Vec = Vec::new(); - let mut items_to_be_processed: Vec = module.body_items.clone(); + let module_def = generate_module_def(&impl_parent, &mut module, old_item_indent); - let new_item_indent = if impl_parent.is_some() { - old_item_indent + 2 - } else { - items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat(); - old_item_indent + 1 - }; - - for item in items_to_be_processed { - let item = item.indent(IndentLevel(1)); - let mut indented_item = String::new(); - format_to!(indented_item, "{new_item_indent}{item}"); - body_items.push(indented_item); - } - - let mut body = body_items.join("\n\n"); - - if let Some(impl_) = &impl_parent { - let mut impl_body_def = String::new(); - - if let Some(self_ty) = impl_.self_ty() { - { - let impl_indent = old_item_indent + 1; - format_to!( - impl_body_def, - "{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}", - ); - } - body = impl_body_def; - - // Add the import for enum/struct corresponding to given impl block - module.make_use_stmt_of_node_with_super(self_ty.syntax()); - for item in module.use_items { - let item_indent = old_item_indent + 1; - body = format!("{item_indent}{item}\n\n{body}"); - } - } - } - - let mut module_def = String::new(); - - let module_name = module.name; - format_to!(module_def, "mod {module_name} {{\n{body}\n{old_item_indent}}}"); - - let mut usages_to_be_updated_for_curr_file = vec![]; - for usages_to_be_updated_for_file in usages_to_be_processed { - if usages_to_be_updated_for_file.0 == ctx.file_id() { - usages_to_be_updated_for_curr_file = usages_to_be_updated_for_file.1; + let mut usages_to_be_processed_for_cur_file = vec![]; + for (file_id, usages) in usages_to_be_processed { + if file_id == ctx.file_id() { + usages_to_be_processed_for_cur_file = usages; continue; } - builder.edit_file(usages_to_be_updated_for_file.0); - for usage_to_be_processed in usages_to_be_updated_for_file.1 { - builder.replace(usage_to_be_processed.0, usage_to_be_processed.1) + builder.edit_file(file_id); + for (text_range, usage) in usages { + builder.replace(text_range, usage) } } builder.edit_file(ctx.file_id()); - for usage_to_be_processed in usages_to_be_updated_for_curr_file { - builder.replace(usage_to_be_processed.0, usage_to_be_processed.1) - } - - for import_path_text_range in import_paths_to_be_removed { - builder.delete(import_path_text_range); + for (text_range, usage) in usages_to_be_processed_for_cur_file { + builder.replace(text_range, usage); } if let Some(impl_) = impl_parent { @@ -199,12 +158,51 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti builder.insert(impl_.syntax().text_range().end(), format!("\n\n{module_def}")); } else { + for import_path_text_range in import_paths_to_be_removed { + if module.text_range.intersect(import_path_text_range).is_some() { + module.text_range = module.text_range.cover(import_path_text_range); + } else { + builder.delete(import_path_text_range); + } + } + builder.replace(module.text_range, module_def) } }, ) } +fn generate_module_def( + parent_impl: &Option, + module: &mut Module, + old_indent: IndentLevel, +) -> String { + let (items_to_be_processed, new_item_indent) = if parent_impl.is_some() { + (Either::Left(module.body_items.iter()), old_indent + 2) + } else { + (Either::Right(module.use_items.iter().chain(module.body_items.iter())), old_indent + 1) + }; + + let mut body = items_to_be_processed + .map(|item| item.indent(IndentLevel(1))) + .map(|item| format!("{new_item_indent}{item}")) + .join("\n\n"); + + if let Some(self_ty) = parent_impl.as_ref().and_then(|imp| imp.self_ty()) { + let impl_indent = old_indent + 1; + body = format!("{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}"); + + // Add the import for enum/struct corresponding to given impl block + module.make_use_stmt_of_node_with_super(self_ty.syntax()); + for item in module.use_items.iter() { + body = format!("{impl_indent}{item}\n\n{body}"); + } + } + + let module_name = module.name; + format!("mod {module_name} {{\n{body}\n{old_indent}}}") +} + #[derive(Debug)] struct Module { text_range: TextRange, @@ -233,20 +231,24 @@ impl Module { fn get_usages_and_record_fields( &self, ctx: &AssistContext<'_>, - ) -> (FxHashMap>, Vec) { + ) -> (FxHashMap>, Vec, FxHashMap) + { let mut adt_fields = Vec::new(); let mut refs: FxHashMap> = FxHashMap::default(); + // use `TextSize` as key to avoid repeated use stmts + let mut use_stmts_to_be_inserted = FxHashMap::default(); //Here impl is not included as each item inside impl will be tied to the parent of //implementing block(a struct, enum, etc), if the parent is in selected module, it will //get updated by ADT section given below or if it is not, then we dont need to do any operation + for item in &self.body_items { match_ast! { match (item.syntax()) { ast::Adt(it) => { if let Some( nod ) = ctx.sema.to_def(&it) { let node_def = Definition::Adt(nod); - self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs); + self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted); //Enum Fields are not allowed to explicitly specify pub, it is implied match it { @@ -280,30 +282,30 @@ impl Module { ast::TypeAlias(it) => { if let Some( nod ) = ctx.sema.to_def(&it) { let node_def = Definition::TypeAlias(nod); - self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs); + self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted); } }, ast::Const(it) => { if let Some( nod ) = ctx.sema.to_def(&it) { let node_def = Definition::Const(nod); - self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs); + self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted); } }, ast::Static(it) => { if let Some( nod ) = ctx.sema.to_def(&it) { let node_def = Definition::Static(nod); - self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs); + self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted); } }, ast::Fn(it) => { if let Some( nod ) = ctx.sema.to_def(&it) { let node_def = Definition::Function(nod); - self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs); + self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted); } }, ast::Macro(it) => { if let Some(nod) = ctx.sema.to_def(&it) { - self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs); + self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs, &mut use_stmts_to_be_inserted); } }, _ => (), @@ -311,7 +313,7 @@ impl Module { } } - (refs, adt_fields) + (refs, adt_fields, use_stmts_to_be_inserted) } fn expand_and_group_usages_file_wise( @@ -319,49 +321,62 @@ impl Module { ctx: &AssistContext<'_>, node_def: Definition, refs_in_files: &mut FxHashMap>, + use_stmts_to_be_inserted: &mut FxHashMap, ) { - for (file_id, references) in node_def.usages(&ctx.sema).all() { + let mod_name = self.name; + let covering_node = match ctx.covering_element() { + syntax::NodeOrToken::Node(node) => node, + syntax::NodeOrToken::Token(tok) => tok.parent().unwrap(), // won't panic + }; + let out_of_sel = |node: &SyntaxNode| !self.text_range.contains_range(node.text_range()); + let mut use_stmts_set = FxHashSet::default(); + + for (file_id, refs) in node_def.usages(&ctx.sema).all() { let source_file = ctx.sema.parse(file_id); - let usages_in_file = references - .into_iter() - .filter_map(|usage| self.get_usage_to_be_processed(&source_file, usage)); - refs_in_files.entry(file_id).or_default().extend(usages_in_file); - } - } + let usages = refs.into_iter().filter_map(|FileReference { range, .. }| { + // handle normal usages + let name_ref = find_node_at_range::(source_file.syntax(), range)?; - fn get_usage_to_be_processed( - &self, - source_file: &SourceFile, - FileReference { range, name, .. }: FileReference, - ) -> Option<(TextRange, String)> { - let path: ast::Path = find_node_at_range(source_file.syntax(), range)?; - - for desc in path.syntax().descendants() { - if desc.to_string() == name.syntax().to_string() - && !self.text_range.contains_range(desc.text_range()) - { - if let Some(name_ref) = ast::NameRef::cast(desc) { - let mod_name = self.name; - return Some(( - name_ref.syntax().text_range(), - format!("{mod_name}::{name_ref}"), - )); + if out_of_sel(name_ref.syntax()) { + let new_ref = format!("{mod_name}::{name_ref}"); + return Some((range, new_ref)); + } else if let Some(use_) = name_ref.syntax().ancestors().find_map(ast::Use::cast) { + // handle usages in use_stmts which is in_sel + // check if `use` is top stmt in selection + if use_.syntax().parent().is_some_and(|parent| parent == covering_node) + && use_stmts_set.insert(use_.syntax().text_range().start()) + { + let use_ = use_stmts_to_be_inserted + .entry(use_.syntax().text_range().start()) + .or_insert_with(|| use_.clone_subtree().clone_for_update()); + for seg in use_ + .syntax() + .descendants() + .filter_map(ast::NameRef::cast) + .filter(|seg| seg.syntax().to_string() == name_ref.to_string()) + { + let new_ref = make::path_from_text(&format!("{mod_name}::{seg}")) + .clone_for_update(); + ted::replace(seg.syntax().parent()?, new_ref.syntax()); + } + } } - } - } - None + None + }); + refs_in_files.entry(file_id).or_default().extend(usages); + } } fn change_visibility(&mut self, record_fields: Vec) { let (mut replacements, record_field_parents, impls) = get_replacements_for_visibility_change(&mut self.body_items, false); - let mut impl_items: Vec = impls + let mut impl_items = impls .into_iter() .flat_map(|impl_| impl_.syntax().descendants()) .filter_map(ast::Item::cast) - .collect(); + .collect_vec(); let (mut impl_item_replacements, _, _) = get_replacements_for_visibility_change(&mut impl_items, true); @@ -394,133 +409,88 @@ impl Module { fn resolve_imports( &mut self, - curr_parent_module: Option, + module: Option, ctx: &AssistContext<'_>, ) -> Vec { - let mut import_paths_to_be_removed: Vec = vec![]; - let mut node_set: FxHashSet = FxHashSet::default(); + let mut imports_to_remove = vec![]; + let mut node_set = FxHashSet::default(); for item in self.body_items.clone() { - for x in item.syntax().descendants() { - if let Some(name) = ast::Name::cast(x.clone()) { - if let Some(name_classify) = NameClass::classify(&ctx.sema, &name) { - //Necessary to avoid two same names going through - if !node_set.contains(&name.syntax().to_string()) { - node_set.insert(name.syntax().to_string()); - let def_opt: Option = match name_classify { - NameClass::Definition(def) => Some(def), - _ => None, - }; - - if let Some(def) = def_opt { - if let Some(import_path) = self - .process_names_and_namerefs_for_import_resolve( - def, - name.syntax(), - &curr_parent_module, - ctx, - ) - { - check_intersection_and_push( - &mut import_paths_to_be_removed, - import_path, - ); - } - } + item.syntax() + .descendants() + .filter_map(|x| { + if let Some(name) = ast::Name::cast(x.clone()) { + NameClass::classify(&ctx.sema, &name).and_then(|nc| match nc { + NameClass::Definition(def) => Some((name.syntax().clone(), def)), + _ => None, + }) + } else if let Some(name_ref) = ast::NameRef::cast(x) { + NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc { + NameRefClass::Definition(def) => Some((name_ref.syntax().clone(), def)), + _ => None, + }) + } else { + None + } + }) + .for_each(|(node, def)| { + if node_set.insert(node.to_string()) { + if let Some(import) = self.process_def_in_sel(def, &node, &module, ctx) { + check_intersection_and_push(&mut imports_to_remove, import); } } - } - - if let Some(name_ref) = ast::NameRef::cast(x) { - if let Some(name_classify) = NameRefClass::classify(&ctx.sema, &name_ref) { - //Necessary to avoid two same names going through - if !node_set.contains(&name_ref.syntax().to_string()) { - node_set.insert(name_ref.syntax().to_string()); - let def_opt: Option = match name_classify { - NameRefClass::Definition(def) => Some(def), - _ => None, - }; - - if let Some(def) = def_opt { - if let Some(import_path) = self - .process_names_and_namerefs_for_import_resolve( - def, - name_ref.syntax(), - &curr_parent_module, - ctx, - ) - { - check_intersection_and_push( - &mut import_paths_to_be_removed, - import_path, - ); - } - } - } - } - } - } + }) } - import_paths_to_be_removed + imports_to_remove } - fn process_names_and_namerefs_for_import_resolve( + fn process_def_in_sel( &mut self, def: Definition, - node_syntax: &SyntaxNode, + use_node: &SyntaxNode, curr_parent_module: &Option, ctx: &AssistContext<'_>, ) -> Option { //We only need to find in the current file let selection_range = ctx.selection_trimmed(); - let curr_file_id = ctx.file_id(); - let search_scope = SearchScope::single_file(curr_file_id); - let usage_res = def.usages(&ctx.sema).in_scope(&search_scope).all(); - let file = ctx.sema.parse(curr_file_id); + let file_id = ctx.file_id(); + let usage_res = def.usages(&ctx.sema).in_scope(&SearchScope::single_file(file_id)).all(); + let file = ctx.sema.parse(file_id); - let mut exists_inside_sel = false; - let mut exists_outside_sel = false; - for (_, refs) in usage_res.iter() { - let mut non_use_nodes_itr = refs.iter().filter_map(|x| { - if find_node_at_range::(file.syntax(), x.range).is_none() { - let path_opt = find_node_at_range::(file.syntax(), x.range); - return path_opt; - } - - None - }); - - if non_use_nodes_itr - .clone() - .any(|x| !selection_range.contains_range(x.syntax().text_range())) + // track uses which does not exists in `Use` + let mut uses_exist_in_sel = false; + let mut uses_exist_out_sel = false; + 'outside: for (_, refs) in usage_res.iter() { + for x in refs + .iter() + .filter(|x| find_node_at_range::(file.syntax(), x.range).is_none()) + .filter_map(|x| find_node_at_range::(file.syntax(), x.range)) { - exists_outside_sel = true; - } - if non_use_nodes_itr.any(|x| selection_range.contains_range(x.syntax().text_range())) { - exists_inside_sel = true; + let in_selection = selection_range.contains_range(x.syntax().text_range()); + uses_exist_in_sel |= in_selection; + uses_exist_out_sel |= !in_selection; + + if uses_exist_in_sel && uses_exist_out_sel { + break 'outside; + } } } - let source_exists_outside_sel_in_same_mod = does_source_exists_outside_sel_in_same_mod( - def, - ctx, - curr_parent_module, - selection_range, - curr_file_id, - ); + let (def_in_mod, def_out_sel) = + check_def_in_mod_and_out_sel(def, ctx, curr_parent_module, selection_range, file_id); - let use_stmt_opt: Option = usage_res.into_iter().find_map(|(file_id, refs)| { - if file_id == curr_file_id { - refs.into_iter() - .rev() - .find_map(|fref| find_node_at_range(file.syntax(), fref.range)) - } else { - None - } + // Find use stmt that use def in current file + let use_stmt: Option = usage_res + .into_iter() + .filter(|(use_file_id, _)| *use_file_id == file_id) + .flat_map(|(_, refs)| refs.into_iter().rev()) + .find_map(|fref| find_node_at_range(file.syntax(), fref.range)); + let use_stmt_not_in_sel = use_stmt.as_ref().is_some_and(|use_stmt| { + !selection_range.contains_range(use_stmt.syntax().text_range()) }); - let mut use_tree_str_opt: Option> = None; + let mut use_tree_paths: Option> = None; //Exists inside and outside selection // - Use stmt for item is present -> get the use_tree_str and reconstruct the path in new // module @@ -534,37 +504,37 @@ impl Module { //get the use_tree_str, reconstruct the use stmt in new module let mut import_path_to_be_removed: Option = None; - if exists_inside_sel && exists_outside_sel { + if uses_exist_in_sel && uses_exist_out_sel { //Changes to be made only inside new module //If use_stmt exists, find the use_tree_str, reconstruct it inside new module //If not, insert a use stmt with super and the given nameref - if let Some((use_tree_str, _)) = - self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax) - { - use_tree_str_opt = Some(use_tree_str); - } else if source_exists_outside_sel_in_same_mod { - //Considered only after use_stmt is not present - //source_exists_outside_sel_in_same_mod | exists_outside_sel(exists_inside_sel = - //true for all cases) - // false | false -> Do nothing - // false | true -> If source is in selection -> nothing to do, If source is outside - // mod -> ust_stmt transversal - // true | false -> super import insertion - // true | true -> super import insertion - self.make_use_stmt_of_node_with_super(node_syntax); + match self.process_use_stmt_for_import_resolve(use_stmt, use_node) { + Some((use_tree_str, _)) => use_tree_paths = Some(use_tree_str), + None if def_in_mod && def_out_sel => { + //Considered only after use_stmt is not present + //def_in_mod && def_out_sel | exists_outside_sel(exists_inside_sel = + //true for all cases) + // false | false -> Do nothing + // false | true -> If source is in selection -> nothing to do, If source is outside + // mod -> ust_stmt transversal + // true | false -> super import insertion + // true | true -> super import insertion + self.make_use_stmt_of_node_with_super(use_node); + } + None => {} } - } else if exists_inside_sel && !exists_outside_sel { + } else if uses_exist_in_sel && !uses_exist_out_sel { //Changes to be made inside new module, and remove import from outside if let Some((mut use_tree_str, text_range_opt)) = - self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax) + self.process_use_stmt_for_import_resolve(use_stmt, use_node) { if let Some(text_range) = text_range_opt { import_path_to_be_removed = Some(text_range); } - if source_exists_outside_sel_in_same_mod { + if def_in_mod && def_out_sel { if let Some(first_path_in_use_tree) = use_tree_str.last() { let first_path_in_use_tree_str = first_path_in_use_tree.to_string(); if !first_path_in_use_tree_str.contains("super") @@ -576,31 +546,43 @@ impl Module { } } - use_tree_str_opt = Some(use_tree_str); - } else if source_exists_outside_sel_in_same_mod { - self.make_use_stmt_of_node_with_super(node_syntax); + use_tree_paths = Some(use_tree_str); + } else if def_in_mod && def_out_sel { + self.make_use_stmt_of_node_with_super(use_node); } } - if let Some(use_tree_str) = use_tree_str_opt { - let mut use_tree_str = use_tree_str; - use_tree_str.reverse(); + if let Some(mut use_tree_paths) = use_tree_paths { + use_tree_paths.reverse(); - if !(!exists_outside_sel && exists_inside_sel && source_exists_outside_sel_in_same_mod) - { - if let Some(first_path_in_use_tree) = use_tree_str.first() { - let first_path_in_use_tree_str = first_path_in_use_tree.to_string(); - if first_path_in_use_tree_str.contains("super") { - let super_path = make::ext::ident_path("super"); - use_tree_str.insert(0, super_path) + if uses_exist_out_sel || !uses_exist_in_sel || !def_in_mod || !def_out_sel { + if let Some(first_path_in_use_tree) = use_tree_paths.first() { + if first_path_in_use_tree.to_string().contains("super") { + use_tree_paths.insert(0, make::ext::ident_path("super")); } } } - let use_ = - make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false)); - let item = ast::Item::from(use_); - self.use_items.insert(0, item); + let is_item = matches!( + def, + Definition::Macro(_) + | Definition::Module(_) + | Definition::Function(_) + | Definition::Adt(_) + | Definition::Const(_) + | Definition::Static(_) + | Definition::Trait(_) + | Definition::TraitAlias(_) + | Definition::TypeAlias(_) + ); + + if (def_out_sel || !is_item) && use_stmt_not_in_sel { + let use_ = make::use_( + None, + make::use_tree(make::join_paths(use_tree_paths), None, None, false), + ); + self.use_items.insert(0, ast::Item::from(use_)); + } } import_path_to_be_removed @@ -621,33 +603,26 @@ impl Module { fn process_use_stmt_for_import_resolve( &self, - use_stmt_opt: Option, + use_stmt: Option, node_syntax: &SyntaxNode, ) -> Option<(Vec, Option)> { - if let Some(use_stmt) = use_stmt_opt { - for desc in use_stmt.syntax().descendants() { - if let Some(path_seg) = ast::PathSegment::cast(desc) { - if path_seg.syntax().to_string() == node_syntax.to_string() { - let mut use_tree_str = vec![path_seg.parent_path()]; - get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str); - for ancs in path_seg.syntax().ancestors() { - //Here we are looking for use_tree with same string value as node - //passed above as the range_to_remove function looks for a comma and - //then includes it in the text range to remove it. But the comma only - //appears at the use_tree level - if let Some(use_tree) = ast::UseTree::cast(ancs) { - if use_tree.syntax().to_string() == node_syntax.to_string() { - return Some(( - use_tree_str, - Some(range_to_remove(use_tree.syntax())), - )); - } - } - } + let use_stmt = use_stmt?; + for path_seg in use_stmt.syntax().descendants().filter_map(ast::PathSegment::cast) { + if path_seg.syntax().to_string() == node_syntax.to_string() { + let mut use_tree_str = vec![path_seg.parent_path()]; + get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str); - return Some((use_tree_str, None)); + //Here we are looking for use_tree with same string value as node + //passed above as the range_to_remove function looks for a comma and + //then includes it in the text range to remove it. But the comma only + //appears at the use_tree level + for use_tree in path_seg.syntax().ancestors().filter_map(ast::UseTree::cast) { + if use_tree.syntax().to_string() == node_syntax.to_string() { + return Some((use_tree_str, Some(range_to_remove(use_tree.syntax())))); } } + + return Some((use_tree_str, None)); } } @@ -676,145 +651,58 @@ fn check_intersection_and_push( import_paths_to_be_removed.push(import_path); } -fn does_source_exists_outside_sel_in_same_mod( +fn check_def_in_mod_and_out_sel( def: Definition, ctx: &AssistContext<'_>, curr_parent_module: &Option, selection_range: TextRange, curr_file_id: FileId, -) -> bool { - let mut source_exists_outside_sel_in_same_mod = false; +) -> (bool, bool) { + macro_rules! check_item { + ($x:ident) => { + if let Some(source) = $x.source(ctx.db()) { + let have_same_parent = if let Some(ast_module) = &curr_parent_module { + ctx.sema.to_module_def(ast_module).is_some_and(|it| it == $x.module(ctx.db())) + } else { + source.file_id.original_file(ctx.db()) == curr_file_id + }; + + let in_sel = !selection_range.contains_range(source.value.syntax().text_range()); + return (have_same_parent, in_sel); + } + }; + } + match def { Definition::Module(x) => { let source = x.definition_source(ctx.db()); - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - if let Some(hir_module) = x.parent(ctx.db()) { - compare_hir_and_ast_module(ast_module, hir_module, ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id + let have_same_parent = match (&curr_parent_module, x.parent(ctx.db())) { + (Some(ast_module), Some(hir_module)) => { + ctx.sema.to_module_def(ast_module).is_some_and(|it| it == hir_module) } - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id + _ => source.file_id.original_file(ctx.db()) == curr_file_id, }; if have_same_parent { if let ModuleSource::Module(module_) = source.value { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(module_.syntax().text_range()); + let in_sel = !selection_range.contains_range(module_.syntax().text_range()); + return (have_same_parent, in_sel); } } - } - Definition::Function(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::Adt(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::Variant(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::Const(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::Static(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::Trait(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } - } - Definition::TypeAlias(x) => { - if let Some(source) = x.source(ctx.db()) { - let have_same_parent = if let Some(ast_module) = &curr_parent_module { - compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some() - } else { - let source_file_id = source.file_id.original_file(ctx.db()); - source_file_id == curr_file_id - }; - - if have_same_parent { - source_exists_outside_sel_in_same_mod = - !selection_range.contains_range(source.value.syntax().text_range()); - } - } + return (have_same_parent, false); } + Definition::Function(x) => check_item!(x), + Definition::Adt(x) => check_item!(x), + Definition::Variant(x) => check_item!(x), + Definition::Const(x) => check_item!(x), + Definition::Static(x) => check_item!(x), + Definition::Trait(x) => check_item!(x), + Definition::TypeAlias(x) => check_item!(x), _ => {} } - source_exists_outside_sel_in_same_mod + (false, false) } fn get_replacements_for_visibility_change( @@ -834,24 +722,30 @@ fn get_replacements_for_visibility_change( *item = item.clone_for_update(); } //Use stmts are ignored + macro_rules! push_to_replacement { + ($it:ident) => { + replacements.push(($it.visibility(), $it.syntax().clone())) + }; + } + match item { - ast::Item::Const(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::Enum(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::ExternCrate(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())), + ast::Item::Const(it) => push_to_replacement!(it), + ast::Item::Enum(it) => push_to_replacement!(it), + ast::Item::ExternCrate(it) => push_to_replacement!(it), + ast::Item::Fn(it) => push_to_replacement!(it), //Associated item's visibility should not be changed ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()), - ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())), + ast::Item::MacroDef(it) => push_to_replacement!(it), + ast::Item::Module(it) => push_to_replacement!(it), + ast::Item::Static(it) => push_to_replacement!(it), ast::Item::Struct(it) => { - replacements.push((it.visibility(), it.syntax().clone())); + push_to_replacement!(it); record_field_parents.push((it.visibility(), it.syntax().clone())); } - ast::Item::Trait(it) => replacements.push((it.visibility(), it.syntax().clone())), - ast::Item::TypeAlias(it) => replacements.push((it.visibility(), it.syntax().clone())), + ast::Item::Trait(it) => push_to_replacement!(it), + ast::Item::TypeAlias(it) => push_to_replacement!(it), ast::Item::Union(it) => { - replacements.push((it.visibility(), it.syntax().clone())); + push_to_replacement!(it); record_field_parents.push((it.visibility(), it.syntax().clone())); } _ => (), @@ -865,8 +759,11 @@ fn get_use_tree_paths_from_path( path: ast::Path, use_tree_str: &mut Vec, ) -> Option<&mut Vec> { - path.syntax().ancestors().filter(|x| x.to_string() != path.to_string()).find_map(|x| { - if let Some(use_tree) = ast::UseTree::cast(x) { + path.syntax() + .ancestors() + .filter(|x| x.to_string() != path.to_string()) + .filter_map(ast::UseTree::cast) + .find_map(|use_tree| { if let Some(upper_tree_path) = use_tree.path() { if upper_tree_path.to_string() != path.to_string() { use_tree_str.push(upper_tree_path.clone()); @@ -874,9 +771,8 @@ fn get_use_tree_paths_from_path( return Some(use_tree); } } - } - None - })?; + None + })?; Some(use_tree_str) } @@ -890,20 +786,6 @@ fn add_change_vis(vis: Option, node_or_token_opt: Option, -) -> Option<()> { - let hir_mod_name = hir_module.name(ctx.db())?; - let ast_mod_name = ast_module.name()?; - if hir_mod_name.display(ctx.db()).to_string() != ast_mod_name.to_string() { - return None; - } - - Some(()) -} - fn indent_range_before_given_node(node: &SyntaxNode) -> Option { node.siblings_with_tokens(syntax::Direction::Prev) .find(|x| x.kind() == WHITESPACE) @@ -1799,6 +1681,54 @@ mod modname { pub(crate) condvar: B, } } +"#, + ); + } + + #[test] + fn test_remove_import_path_inside_selection() { + check_assist( + extract_module, + r#" +$0struct Point; +impl Point { + pub const fn direction(self, other: Self) -> Option { + Some(Vertical) + } +} + +pub enum Direction { + Horizontal, + Vertical, +} +use Direction::{Horizontal, Vertical};$0 + +fn main() { + let x = Vertical; +} +"#, + r#" +mod modname { + use Direction::{Horizontal, Vertical}; + + pub(crate) struct Point; + + impl Point { + pub const fn direction(self, other: Self) -> Option { + Some(Vertical) + } + } + + pub enum Direction { + Horizontal, + Vertical, + } +} +use modname::Direction::{Horizontal, Vertical}; + +fn main() { + let x = Vertical; +} "#, ); } diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index fe2f8ed641..ff051fa870 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -198,7 +198,7 @@ fn get_adt_source( adt: &hir::Adt, fn_name: &str, ) -> Option<(Option, FileId)> { - let range = adt.source(ctx.sema.db)?.syntax().original_file_range(ctx.sema.db); + let range = adt.source(ctx.sema.db)?.syntax().original_file_range_rooted(ctx.sema.db); let file = ctx.sema.parse(range.file_id); let adt_source = ctx.sema.find_node_at_offset_with_macros(file.syntax(), range.range.start())?; diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 50ec4347dc..a90fe83857 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -206,7 +206,7 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let fn_body = fn_source.value.body()?; let param_list = fn_source.value.param_list()?; - let FileRange { file_id, range } = fn_source.syntax().original_file_range(ctx.sema.db); + let FileRange { file_id, range } = fn_source.syntax().original_file_range_rooted(ctx.sema.db); if file_id == ctx.file_id() && range.contains(ctx.offset()) { cov_mark::hit!(inline_call_recursive); return None; diff --git a/crates/ide-completion/src/completions/env_vars.rs b/crates/ide-completion/src/completions/env_vars.rs index 35e6b97eb7..4005753773 100644 --- a/crates/ide-completion/src/completions/env_vars.rs +++ b/crates/ide-completion/src/completions/env_vars.rs @@ -1,7 +1,10 @@ //! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html) -use hir::Semantics; -use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase}; -use syntax::ast::{self, IsString}; +use hir::MacroFileIdExt; +use ide_db::syntax_helpers::node_ext::macro_call_for_string_token; +use syntax::{ + ast::{self, IsString}, + AstToken, +}; use crate::{ completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind, @@ -32,10 +35,24 @@ const CARGO_DEFINED_VARS: &[(&str, &str)] = &[ pub(crate) fn complete_cargo_env_vars( acc: &mut Completions, ctx: &CompletionContext<'_>, + original: &ast::String, expanded: &ast::String, ) -> Option<()> { - guard_env_macro(expanded, &ctx.sema)?; - let range = expanded.text_range_between_quotes()?; + let is_in_env_expansion = ctx + .sema + .hir_file_for(&expanded.syntax().parent()?) + .macro_file() + .map_or(false, |it| it.is_env_or_option_env(ctx.sema.db)); + if !is_in_env_expansion { + let call = macro_call_for_string_token(expanded)?; + let makro = ctx.sema.resolve_macro_call(&call)?; + // We won't map into `option_env` as that generates `None` for non-existent env vars + // so fall back to this lookup + if !makro.is_env_or_option_env(ctx.sema.db) { + return None; + } + } + let range = original.text_range_between_quotes()?; CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| { let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var); @@ -46,18 +63,6 @@ pub(crate) fn complete_cargo_env_vars( Some(()) } -fn guard_env_macro(string: &ast::String, semantics: &Semantics<'_, RootDatabase>) -> Option<()> { - let call = macro_call_for_string_token(string)?; - let name = call.path()?.segment()?.name_ref()?; - let makro = semantics.resolve_macro_call(&call)?; - let db = semantics.db; - - match name.text().as_str() { - "env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()), - _ => None, - } -} - #[cfg(test)] mod tests { use crate::tests::{check_edit, completion_list}; @@ -68,7 +73,7 @@ mod tests { &format!( r#" #[rustc_builtin_macro] - macro_rules! {macro_name} {{ + macro {macro_name} {{ ($var:literal) => {{ 0 }} }} @@ -80,7 +85,7 @@ mod tests { &format!( r#" #[rustc_builtin_macro] - macro_rules! {macro_name} {{ + macro {macro_name} {{ ($var:literal) => {{ 0 }} }} diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 7394d63be5..7946784150 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -96,7 +96,7 @@ fn complete_trait_impl_name( .parent() } }?; - let item = ctx.sema.original_syntax_node(&item)?; + let item = ctx.sema.original_syntax_node_rooted(&item)?; // item -> ASSOC_ITEM_LIST -> IMPL let impl_def = ast::Impl::cast(item.parent()?.parent()?)?; let replacement_range = { diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index ecf5b29e2c..c2faa2d939 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -2,7 +2,7 @@ use std::iter; -use hir::{HirFileIdExt, Module, ModuleSource}; +use hir::{HirFileIdExt, Module}; use ide_db::{ base_db::{SourceDatabaseExt, VfsPath}, FxHashSet, RootDatabase, SymbolKind, @@ -57,7 +57,7 @@ pub(crate) fn complete_mod( .collect::>(); let module_declaration_file = - current_module.declaration_source(ctx.db).map(|module_declaration_source_file| { + current_module.declaration_source_range(ctx.db).map(|module_declaration_source_file| { module_declaration_source_file.file_id.original_file(ctx.db) }); @@ -148,9 +148,7 @@ fn module_chain_to_containing_module_file( ) -> Vec { let mut path = iter::successors(Some(current_module), |current_module| current_module.parent(db)) - .take_while(|current_module| { - matches!(current_module.definition_source(db).value, ModuleSource::Module(_)) - }) + .take_while(|current_module| current_module.is_inline(db)) .collect::>(); path.reverse(); path diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 4bab288685..357060817c 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -369,6 +369,7 @@ impl CompletionItemKind { SymbolKind::LifetimeParam => "lt", SymbolKind::Local => "lc", SymbolKind::Macro => "ma", + SymbolKind::ProcMacro => "pm", SymbolKind::Module => "md", SymbolKind::SelfParam => "sp", SymbolKind::SelfType => "sy", diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 912f2fba2b..d89cfc8b6c 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -207,7 +207,7 @@ pub fn completions( CompletionAnalysis::String { original, expanded: Some(expanded) } => { completions::extern_abi::complete_extern_abi(acc, ctx, expanded); completions::format_string::format_string(acc, ctx, original, expanded); - completions::env_vars::complete_cargo_env_vars(acc, ctx, expanded); + completions::env_vars::complete_cargo_env_vars(acc, ctx, original, expanded); } CompletionAnalysis::UnexpandedAttrTT { colon_prefix, diff --git a/crates/ide-db/src/apply_change.rs b/crates/ide-db/src/apply_change.rs index 017635d88e..ec05f6d13d 100644 --- a/crates/ide-db/src/apply_change.rs +++ b/crates/ide-db/src/apply_change.rs @@ -205,6 +205,7 @@ impl RootDatabase { // SourceDatabaseExt base_db::FileTextQuery + base_db::CompressedFileTextQuery base_db::FileSourceRootQuery base_db::SourceRootQuery base_db::SourceRootCratesQuery diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 33970de1e4..c0f0faba35 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -407,7 +407,7 @@ impl NameClass { } pub fn classify(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option { - let _p = tracing::span!(tracing::Level::INFO, "classify_name").entered(); + let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify").entered(); let parent = name.syntax().parent()?; @@ -499,7 +499,8 @@ impl NameClass { sema: &Semantics<'_, RootDatabase>, lifetime: &ast::Lifetime, ) -> Option { - let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime", ?lifetime).entered(); + let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify_lifetime", ?lifetime) + .entered(); let parent = lifetime.syntax().parent()?; if let Some(it) = ast::LifetimeParam::cast(parent.clone()) { @@ -590,7 +591,8 @@ impl NameRefClass { sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef, ) -> Option { - let _p = tracing::span!(tracing::Level::INFO, "classify_name_ref", ?name_ref).entered(); + let _p = + tracing::span!(tracing::Level::INFO, "NameRefClass::classify", ?name_ref).entered(); let parent = name_ref.syntax().parent()?; @@ -689,7 +691,8 @@ impl NameRefClass { sema: &Semantics<'_, RootDatabase>, lifetime: &ast::Lifetime, ) -> Option { - let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime_ref", ?lifetime).entered(); + let _p = tracing::span!(tracing::Level::INFO, "NameRefClass::classify_lifetime", ?lifetime) + .entered(); let parent = lifetime.syntax().parent()?; match parent.kind() { SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => { diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs index 4ac8a7c4c4..db44b1e723 100644 --- a/crates/ide-db/src/helpers.rs +++ b/crates/ide-db/src/helpers.rs @@ -71,7 +71,7 @@ pub fn visit_file_defs( let mut defs: VecDeque<_> = module.declarations(db).into(); while let Some(def) = defs.pop_front() { if let ModuleDef::Module(submodule) = def { - if let hir::ModuleSource::Module(_) = submodule.definition_source(db).value { + if submodule.is_inline(db) { defs.extend(submodule.declarations(db)); submodule.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into())); } diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index be08b37bac..0d5a93f7b8 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -51,6 +51,7 @@ use std::{fmt, mem::ManuallyDrop}; use base_db::{ salsa::{self, Durability}, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, + DEFAULT_FILE_TEXT_LRU_CAP, }; use hir::db::{DefDatabase, ExpandDatabase, HirDatabase}; use triomphe::Arc; @@ -157,6 +158,7 @@ impl RootDatabase { pub fn update_base_query_lru_capacities(&mut self, lru_capacity: Option) { let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_PARSE_LRU_CAP); + base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP); base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity); // macro expansions are usually rather small, so we can afford to keep more of them alive hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(4 * lru_capacity); @@ -166,6 +168,7 @@ impl RootDatabase { pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap, usize>) { use hir::db as hir_db; + base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP); base_db::ParseQuery.in_db_mut(self).set_lru_capacity( lru_capacities .get(stringify!(ParseQuery)) @@ -199,7 +202,7 @@ impl RootDatabase { // base_db::ProcMacrosQuery // SourceDatabaseExt - // base_db::FileTextQuery + base_db::FileTextQuery // base_db::FileSourceRootQuery // base_db::SourceRootQuery base_db::SourceRootCratesQuery @@ -348,6 +351,7 @@ pub enum SymbolKind { LifetimeParam, Local, Macro, + ProcMacro, Module, SelfParam, SelfType, @@ -366,9 +370,8 @@ pub enum SymbolKind { impl From for SymbolKind { fn from(it: hir::MacroKind) -> Self { match it { - hir::MacroKind::Declarative | hir::MacroKind::BuiltIn | hir::MacroKind::ProcMacro => { - SymbolKind::Macro - } + hir::MacroKind::Declarative | hir::MacroKind::BuiltIn => SymbolKind::Macro, + hir::MacroKind::ProcMacro => SymbolKind::ProcMacro, hir::MacroKind::Derive => SymbolKind::Derive, hir::MacroKind::Attr => SymbolKind::Attribute, } @@ -381,6 +384,7 @@ impl From for SymbolKind { hir::ModuleDefId::ConstId(..) => SymbolKind::Const, hir::ModuleDefId::EnumVariantId(..) => SymbolKind::Variant, hir::ModuleDefId::FunctionId(..) => SymbolKind::Function, + hir::ModuleDefId::MacroId(hir::MacroId::ProcMacroId(..)) => SymbolKind::ProcMacro, hir::ModuleDefId::MacroId(..) => SymbolKind::Macro, hir::ModuleDefId::ModuleId(..) => SymbolKind::Module, hir::ModuleDefId::StaticId(..) => SymbolKind::Static, diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 006d8882c1..a3ecc10360 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -190,22 +190,15 @@ impl SearchScope { let mut entries = IntMap::default(); let (file_id, range) = { - let InFile { file_id, value } = module.definition_source(db); + let InFile { file_id, value } = module.definition_source_range(db); if let Some(InRealFile { file_id, value: call_source }) = file_id.original_call_node(db) { (file_id, Some(call_source.text_range())) } else { - ( - file_id.original_file(db), - match value { - ModuleSource::SourceFile(_) => None, - ModuleSource::Module(it) => Some(it.syntax().text_range()), - ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()), - }, - ) + (file_id.original_file(db), Some(value)) } }; - entries.insert(file_id, range); + entries.entry(file_id).or_insert(range); let mut to_visit: Vec<_> = module.children(db).collect(); while let Some(module) = to_visit.pop() { diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs index db28928a24..a0fad7c850 100644 --- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -38,7 +38,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option, d: &hir::UnresolvedField) -> Option> { + let mut fixes = Vec::new(); if d.method_with_same_name_exists { - method_fix(ctx, &d.expr) - } else { - // FIXME: add quickfix - - None + fixes.extend(method_fix(ctx, &d.expr)); } + fixes.extend(field_fix(ctx, d)); + if fixes.is_empty() { + None + } else { + Some(fixes) + } +} + +// FIXME: Add Snippet Support +fn field_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option { + // Get the FileRange of the invalid field access + let root = ctx.sema.db.parse_or_expand(d.expr.file_id); + let expr = d.expr.value.to_node(&root); + + let error_range = ctx.sema.original_range_opt(expr.syntax())?; + let field_name = d.name.as_str()?; + // Convert the receiver to an ADT + let adt = d.receiver.strip_references().as_adt()?; + let target_module = adt.module(ctx.sema.db); + + let suggested_type = + if let Some(new_field_type) = ctx.sema.type_of_expr(&expr).map(|v| v.adjusted()) { + let display = + new_field_type.display_source_code(ctx.sema.db, target_module.into(), false).ok(); + make::ty(display.as_deref().unwrap_or("()")) + } else { + make::ty("()") + }; + + if !is_editable_crate(target_module.krate(), ctx.sema.db) { + return None; + } + + match adt { + Adt::Struct(adt_struct) => { + add_field_to_struct_fix(ctx, adt_struct, field_name, suggested_type, error_range) + } + Adt::Union(adt_union) => { + add_variant_to_union(ctx, adt_union, field_name, suggested_type, error_range) + } + _ => None, + } +} + +fn add_variant_to_union( + ctx: &DiagnosticsContext<'_>, + adt_union: Union, + field_name: &str, + suggested_type: Type, + error_range: FileRange, +) -> Option { + let adt_source = adt_union.source(ctx.sema.db)?; + let adt_syntax = adt_source.syntax(); + let field_list = adt_source.value.record_field_list()?; + let range = adt_syntax.original_file_range_rooted(ctx.sema.db); + let field_name = make::name(field_name); + + let (offset, record_field) = + record_field_layout(None, field_name, suggested_type, field_list, adt_syntax.value)?; + + let mut src_change_builder = SourceChangeBuilder::new(range.file_id); + src_change_builder.insert(offset, record_field); + Some(Assist { + id: AssistId("add-variant-to-union", AssistKind::QuickFix), + label: Label::new("Add field to union".to_owned()), + group: None, + target: error_range.range, + source_change: Some(src_change_builder.finish()), + trigger_signature_help: false, + }) +} + +fn add_field_to_struct_fix( + ctx: &DiagnosticsContext<'_>, + adt_struct: Struct, + field_name: &str, + suggested_type: Type, + error_range: FileRange, +) -> Option { + let struct_source = adt_struct.source(ctx.sema.db)?; + let struct_syntax = struct_source.syntax(); + let struct_range = struct_syntax.original_file_range_rooted(ctx.sema.db); + let field_list = struct_source.value.field_list(); + match field_list { + Some(FieldList::RecordFieldList(field_list)) => { + // Get range of final field in the struct + let visibility = if error_range.file_id == struct_range.file_id { + None + } else { + Some(make::visibility_pub_crate()) + }; + let field_name = make::name(field_name); + + let (offset, record_field) = record_field_layout( + visibility, + field_name, + suggested_type, + field_list, + struct_syntax.value, + )?; + + let mut src_change_builder = SourceChangeBuilder::new(struct_range.file_id); + + // FIXME: Allow for choosing a visibility modifier see https://github.com/rust-lang/rust-analyzer/issues/11563 + src_change_builder.insert(offset, record_field); + Some(Assist { + id: AssistId("add-field-to-record-struct", AssistKind::QuickFix), + label: Label::new("Add field to Record Struct".to_owned()), + group: None, + target: error_range.range, + source_change: Some(src_change_builder.finish()), + trigger_signature_help: false, + }) + } + None => { + // Add a field list to the Unit Struct + let mut src_change_builder = SourceChangeBuilder::new(struct_range.file_id); + let field_name = make::name(field_name); + let visibility = if error_range.file_id == struct_range.file_id { + None + } else { + Some(make::visibility_pub_crate()) + }; + // FIXME: Allow for choosing a visibility modifier see https://github.com/rust-lang/rust-analyzer/issues/11563 + let indent = IndentLevel::from_node(struct_syntax.value) + 1; + + let field = make::record_field(visibility, field_name, suggested_type).indent(indent); + let record_field_list = make::record_field_list(iter::once(field)); + // A Unit Struct with no `;` is invalid syntax. We should not suggest this fix. + let semi_colon = + algo::skip_trivia_token(struct_syntax.value.last_token()?, Direction::Prev)?; + if semi_colon.kind() != SyntaxKind::SEMICOLON { + return None; + } + src_change_builder.replace(semi_colon.text_range(), record_field_list.to_string()); + + Some(Assist { + id: AssistId("convert-unit-struct-to-record-struct", AssistKind::QuickFix), + label: Label::new("Convert Unit Struct to Record Struct and add field".to_owned()), + group: None, + target: error_range.range, + source_change: Some(src_change_builder.finish()), + trigger_signature_help: false, + }) + } + Some(FieldList::TupleFieldList(_tuple)) => { + // FIXME: Add support for Tuple Structs. Tuple Structs are not sent to this diagnostic + None + } + } +} + +/// Used to determine the layout of the record field in the struct. +fn record_field_layout( + visibility: Option, + name: Name, + suggested_type: Type, + field_list: ast::RecordFieldList, + struct_syntax: &SyntaxNode, +) -> Option<(TextSize, String)> { + let (offset, needs_comma, trailing_new_line, indent) = match field_list.fields().last() { + Some(record_field) => { + let syntax = algo::skip_trivia_token(field_list.r_curly_token()?, Direction::Prev)?; + + let last_field_syntax = record_field.syntax(); + let last_field_indent = IndentLevel::from_node(last_field_syntax); + ( + last_field_syntax.text_range().end(), + syntax.kind() != SyntaxKind::COMMA, + false, + last_field_indent, + ) + } + // Empty Struct. Add a field right before the closing brace + None => { + let indent = IndentLevel::from_node(struct_syntax) + 1; + let offset = field_list.r_curly_token()?.text_range().start(); + (offset, false, true, indent) + } + }; + let comma = if needs_comma { ",\n" } else { "" }; + let trailing_new_line = if trailing_new_line { "\n" } else { "" }; + let record_field = make::record_field(visibility, name, suggested_type); + + Some((offset, format!("{comma}{indent}{record_field}{trailing_new_line}"))) } // FIXME: We should fill out the call here, move the cursor and trigger signature help fn method_fix( ctx: &DiagnosticsContext<'_>, expr_ptr: &InFile>, -) -> Option> { +) -> Option { let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); let expr = expr_ptr.value.to_node(&root); let FileRange { range, file_id } = ctx.sema.original_range_opt(expr.syntax())?; - Some(vec![Assist { + Some(Assist { id: AssistId("expected-field-found-method-call-fix", AssistKind::QuickFix), label: Label::new("Use parentheses to call the method".to_owned()), group: None, @@ -73,13 +266,15 @@ fn method_fix( TextEdit::insert(range.end(), "()".to_owned()), )), trigger_signature_help: false, - }]) + }) } #[cfg(test)] mod tests { + use crate::{ tests::{ check_diagnostics, check_diagnostics_with_config, check_diagnostics_with_disabled, + check_fix, }, DiagnosticsConfig, }; @@ -168,4 +363,100 @@ fn foo() { config.disabled.insert("syntax-error".to_owned()); check_diagnostics_with_config(config, "fn foo() { (). }"); } + + #[test] + fn unresolved_field_fix_on_unit() { + check_fix( + r#" + struct Foo; + + fn foo() { + Foo.bar$0; + } + "#, + r#" + struct Foo{ bar: () } + + fn foo() { + Foo.bar; + } + "#, + ); + } + #[test] + fn unresolved_field_fix_on_empty() { + check_fix( + r#" + struct Foo{ + } + + fn foo() { + let foo = Foo{}; + foo.bar$0; + } + "#, + r#" + struct Foo{ + bar: () + } + + fn foo() { + let foo = Foo{}; + foo.bar; + } + "#, + ); + } + #[test] + fn unresolved_field_fix_on_struct() { + check_fix( + r#" + struct Foo{ + a: i32 + } + + fn foo() { + let foo = Foo{a: 0}; + foo.bar$0; + } + "#, + r#" + struct Foo{ + a: i32, + bar: () + } + + fn foo() { + let foo = Foo{a: 0}; + foo.bar; + } + "#, + ); + } + #[test] + fn unresolved_field_fix_on_union() { + check_fix( + r#" + union Foo{ + a: i32 + } + + fn foo() { + let foo = Foo{a: 0}; + foo.bar$0; + } + "#, + r#" + union Foo{ + a: i32, + bar: () + } + + fn foo() { + let foo = Foo{a: 0}; + foo.bar; + } + "#, + ); + } } diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs index 28ccf474b4..a9e1d07d7c 100644 --- a/crates/ide-diagnostics/src/handlers/unused_variables.rs +++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs @@ -1,3 +1,11 @@ +use ide_db::{ + assists::{Assist, AssistId, AssistKind}, + base_db::FileRange, + label::Label, + source_change::SourceChange, +}; +use text_edit::TextEdit; + use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; // Diagnostic: unused-variables @@ -8,18 +16,38 @@ pub(crate) fn unused_variables( d: &hir::UnusedVariable, ) -> Diagnostic { let ast = d.local.primary_source(ctx.sema.db).syntax_ptr(); + let diagnostic_range = ctx.sema.diagnostics_display_range(ast); + let var_name = d.local.primary_source(ctx.sema.db).syntax().to_string(); Diagnostic::new_with_syntax_node_ptr( ctx, DiagnosticCode::RustcLint("unused_variables"), "unused variable", ast, ) + .with_fixes(fixes(&var_name, diagnostic_range, ast.file_id.is_macro())) .experimental() } +fn fixes(var_name: &String, diagnostic_range: FileRange, is_in_marco: bool) -> Option> { + if is_in_marco { + return None; + } + Some(vec![Assist { + id: AssistId("unscore_unused_variable_name", AssistKind::QuickFix), + label: Label::new(format!("Rename unused {} to _{}", var_name, var_name)), + group: None, + target: diagnostic_range.range, + source_change: Some(SourceChange::from_text_edit( + diagnostic_range.file_id, + TextEdit::replace(diagnostic_range.range, format!("_{}", var_name)), + )), + trigger_signature_help: false, + }]) +} + #[cfg(test)] mod tests { - use crate::tests::check_diagnostics; + use crate::tests::{check_diagnostics, check_fix, check_no_fix}; #[test] fn unused_variables_simple() { @@ -29,23 +57,23 @@ mod tests { struct Foo { f1: i32, f2: i64 } fn f(kkk: i32) {} - //^^^ warn: unused variable + //^^^ 💡 warn: unused variable fn main() { let a = 2; - //^ warn: unused variable + //^ 💡 warn: unused variable let b = 5; // note: `unused variable` implies `unused mut`, so we should not emit both at the same time. let mut c = f(b); - //^^^^^ warn: unused variable + //^^^^^ 💡 warn: unused variable let (d, e) = (3, 5); - //^ warn: unused variable + //^ 💡 warn: unused variable let _ = e; let f1 = 2; let f2 = 5; let f = Foo { f1, f2 }; match f { Foo { f1, f2 } => { - //^^ warn: unused variable + //^^ 💡 warn: unused variable _ = f2; } } @@ -53,7 +81,7 @@ fn main() { if g {} let h: fn() -> i32 = || 2; let i = h(); - //^ warn: unused variable + //^ 💡 warn: unused variable } "#, ); @@ -67,11 +95,11 @@ struct S { } impl S { fn owned_self(self, u: i32) {} - //^ warn: unused variable + //^ 💡 warn: unused variable fn ref_self(&self, u: i32) {} - //^ warn: unused variable + //^ 💡 warn: unused variable fn ref_mut_self(&mut self, u: i32) {} - //^ warn: unused variable + //^ 💡 warn: unused variable fn owned_mut_self(mut self) {} //^^^^^^^^ 💡 warn: variable does not need to be mutable @@ -103,7 +131,78 @@ fn main() { #[deny(unused)] fn main2() { let x = 2; - //^ error: unused variable + //^ 💡 error: unused variable +} +"#, + ); + } + + #[test] + fn fix_unused_variable() { + check_fix( + r#" +fn main() { + let x$0 = 2; +} +"#, + r#" +fn main() { + let _x = 2; +} +"#, + ); + + check_fix( + r#" +fn main() { + let ($0d, _e) = (3, 5); +} +"#, + r#" +fn main() { + let (_d, _e) = (3, 5); +} +"#, + ); + + check_fix( + r#" +struct Foo { f1: i32, f2: i64 } +fn main() { + let f = Foo { f1: 0, f2: 0 }; + match f { + Foo { f1$0, f2 } => { + _ = f2; + } + } +} +"#, + r#" +struct Foo { f1: i32, f2: i64 } +fn main() { + let f = Foo { f1: 0, f2: 0 }; + match f { + Foo { _f1, f2 } => { + _ = f2; + } + } +} +"#, + ); + } + + #[test] + fn no_fix_for_marco() { + check_no_fix( + r#" +macro_rules! my_macro { + () => { + let x = 3; + }; +} + +fn main() { + $0my_macro!(); } "#, ); diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs index fb98e95684..cfda1c692a 100644 --- a/crates/ide-ssr/src/matching.rs +++ b/crates/ide-ssr/src/matching.rs @@ -125,9 +125,12 @@ impl<'db, 'sema> Matcher<'db, 'sema> { let match_state = Matcher { sema, restrict_range: *restrict_range, rule }; // First pass at matching, where we check that node types and idents match. match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?; - match_state.validate_range(&sema.original_range(code))?; + let file_range = sema + .original_range_opt(code) + .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?; + match_state.validate_range(&file_range)?; let mut the_match = Match { - range: sema.original_range(code), + range: file_range, matched_node: code.clone(), placeholder_values: FxHashMap::default(), ignored_comments: Vec::new(), @@ -175,7 +178,10 @@ impl<'db, 'sema> Matcher<'db, 'sema> { self.check_constraint(constraint, code)?; } if let Phase::Second(matches_out) = phase { - let original_range = self.sema.original_range(code); + let original_range = self + .sema + .original_range_opt(code) + .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?; // We validated the range for the node when we started the match, so the placeholder // probably can't fail range validation, but just to be safe... self.validate_range(&original_range)?; @@ -487,7 +493,13 @@ impl<'db, 'sema> Matcher<'db, 'sema> { match_out.placeholder_values.insert( placeholder.ident.clone(), PlaceholderMatch::from_range(FileRange { - file_id: self.sema.original_range(code).file_id, + file_id: self + .sema + .original_range_opt(code) + .ok_or(MatchFailed { + reason: Some("def site definition".to_owned()), + })? + .file_id, range: first_matched_token .text_range() .cover(last_matched_token.text_range()), diff --git a/crates/ide-ssr/src/search.rs b/crates/ide-ssr/src/search.rs index 8d2d796122..55a49da242 100644 --- a/crates/ide-ssr/src/search.rs +++ b/crates/ide-ssr/src/search.rs @@ -190,12 +190,9 @@ impl MatchFinder<'_> { // When matching within a macro expansion, we only want to allow matches of // nodes that originated entirely from within the token tree of the macro call. // i.e. we don't want to match something that came from the macro itself. - self.slow_scan_node( - &expanded, - rule, - &Some(self.sema.original_range(tt.syntax())), - matches_out, - ); + if let Some(range) = self.sema.original_range_opt(tt.syntax()) { + self.slow_scan_node(&expanded, rule, &Some(range), matches_out); + } } } } @@ -227,7 +224,7 @@ impl MatchFinder<'_> { // There is no range restriction. return true; } - let node_range = self.sema.original_range(code); + let Some(node_range) = self.sema.original_range_opt(code) else { return false }; for range in &self.restrict_ranges { if range.file_id == node_range.file_id && range.range.contains_range(node_range.range) { return true; diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 1bda15255d..ddeeca5f7b 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1,10 +1,10 @@ -use std::mem::discriminant; +use std::{iter, mem::discriminant}; use crate::{ doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, RangeInfo, TryToNav, }; -use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics}; +use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileId, FileLoader}, defs::{Definition, IdentClass}, @@ -74,11 +74,13 @@ pub(crate) fn goto_definition( .filter_map(|token| { let parent = token.parent()?; - if let Some(tt) = ast::TokenTree::cast(parent.clone()) { - if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) { + if let Some(token) = ast::String::cast(token.clone()) { + if let Some(x) = try_lookup_include_path(sema, token, file_id) { return Some(vec![x]); } + } + if ast::TokenTree::can_cast(parent.kind()) { if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token) { return Some(vec![x]); } @@ -111,24 +113,17 @@ pub(crate) fn goto_definition( fn try_lookup_include_path( sema: &Semantics<'_, RootDatabase>, - tt: ast::TokenTree, - token: SyntaxToken, + token: ast::String, file_id: FileId, ) -> Option { - let token = ast::String::cast(token)?; - let path = token.value()?.into_owned(); - let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; - let name = macro_call.path()?.segment()?.name_ref()?; - if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") { + let file = sema.hir_file_for(&token.syntax().parent()?).macro_file()?; + if !iter::successors(Some(file), |file| file.parent(sema.db).macro_file()) + // Check that we are in the eager argument expansion of an include macro + .any(|file| file.is_include_like_macro(sema.db) && file.eager_arg(sema.db).is_none()) + { return None; } - - // Ignore non-built-in macros to account for shadowing - if let Some(it) = sema.resolve_macro_call(¯o_call) { - if !matches!(it.kind(sema.db), hir::MacroKind::BuiltIn) { - return None; - } - } + let path = token.value()?; let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; let size = sema.db.file_text(file_id).len().try_into().ok()?; @@ -1531,6 +1526,26 @@ fn main() { ); } + #[test] + fn goto_include_has_eager_input() { + check( + r#" +//- /main.rs +#[rustc_builtin_macro] +macro_rules! include_str {} +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { + let str = include_str!(concat!("foo", ".tx$0t")); +} +//- /foo.txt +// empty +//^file +"#, + ); + } + #[test] fn goto_doc_include_str() { check( diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index d1d039534d..63777d4910 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -510,7 +510,7 @@ fn render_notable_trait_comment( let mut needs_impl_header = true; for (trait_, assoc_types) in notable_traits { desc.push_str(if mem::take(&mut needs_impl_header) { - " // Implements notable traits: " + "// Implements notable traits: " } else { ", " }); @@ -661,7 +661,7 @@ fn closure_ty( if let Some(layout) = render_memory_layout(config.memory_layout, || original.layout(sema.db), |_| None, |_| None) { - format_to!(markup, "{layout}"); + format_to!(markup, " {layout}"); } if let Some(trait_) = c.fn_trait(sema.db).get_id(sema.db, original.krate(sema.db).into()) { push_new_def(hir::Trait::from(trait_).into()) @@ -730,7 +730,7 @@ fn render_memory_layout( let config = config?; let layout = layout().ok()?; - let mut label = String::from(" // "); + let mut label = String::from("// "); if let Some(render) = config.size { let size = match tag(&layout) { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index c3cd6513dc..4451e31870 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -180,7 +180,7 @@ fn foo() { *local* ```rust - // size = 4, align = 4 + // size = 4, align = 4 let local: i32 ``` "#]], @@ -471,7 +471,7 @@ fn main() { *iter* ```rust - // size = 8, align = 4 + // size = 8, align = 4 let mut iter: Iter>, impl Fn(&mut u32, &u32, &mut u32) -> Option, u32>> ``` "#]], @@ -713,7 +713,7 @@ struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } ``` ```rust - // size = 1, align = 1, offset = 6 + // size = 1, align = 1, offset = 6 field_a: u8 ``` "#]], @@ -739,7 +739,7 @@ fn main() { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 pub field_a: u32 ``` "#]], @@ -762,7 +762,7 @@ fn main() { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 pub field_a: u32 ``` "#]], @@ -787,7 +787,7 @@ fn main() { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 pub 0: u32 ``` "#]], @@ -808,7 +808,7 @@ fn foo(foo: Foo) { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 pub 0: u32 ``` "#]], @@ -819,7 +819,7 @@ fn foo(foo: Foo) { fn hover_tuple_struct() { check( r#" -struct Foo$0(pub u32) +struct Foo$0(pub u32) where u32: Copy; "#, expect![[r#" *Foo* @@ -829,8 +829,100 @@ struct Foo$0(pub u32) ``` ```rust - // size = 4, align = 4 - struct Foo(pub u32); + // size = 4, align = 4 + struct Foo(pub u32) + where + u32: Copy, + ``` + "#]], + ); +} + +#[test] +fn hover_record_struct() { + check( + r#" +struct Foo$0 { field: u32 } +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + // size = 4, align = 4 + struct Foo { + field: u32, + } + ``` + "#]], + ); + check( + r#" +struct Foo$0 where u32: Copy { field: u32 } +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + // size = 4, align = 4 + struct Foo + where + u32: Copy, + { + field: u32, + } + ``` + "#]], + ); +} + +#[test] +fn hover_unit_struct() { + check( + r#" +struct Foo$0 where u32: Copy; +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + // size = 0, align = 1 + struct Foo + where + u32: Copy, + ``` + "#]], + ); +} + +#[test] +fn hover_type_alias() { + check( + r#" +type Fo$0o: Trait = S where T: Trait; +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + type Foo: Trait = S + where + T: Trait, ``` "#]], ); @@ -957,7 +1049,7 @@ fn main() { *zz* ```rust - // size = 8, align = 4 + // size = 8, align = 4 let zz: Test ``` "#]], @@ -1009,7 +1101,7 @@ fn main() { let b$0ar = Some(12); } *bar* ```rust - // size = 4, align = 4 + // size = 4, align = 4 let bar: Option ``` "#]], @@ -1079,7 +1171,7 @@ fn hover_for_local_variable() { *foo* ```rust - // size = 4, align = 4 + // size = 4, align = 4 foo: i32 ``` "#]], @@ -1094,7 +1186,7 @@ fn hover_for_local_variable_pat() { *foo* ```rust - // size = 4, align = 4 + // size = 4, align = 4 foo: i32 ``` "#]], @@ -1109,7 +1201,7 @@ fn hover_local_var_edge() { *foo* ```rust - // size = 4, align = 4 + // size = 4, align = 4 foo: i32 ``` "#]], @@ -1124,7 +1216,7 @@ fn hover_for_param_edge() { *foo* ```rust - // size = 4, align = 4 + // size = 4, align = 4 foo: i32 ``` "#]], @@ -1169,7 +1261,7 @@ fn main() { let foo_$0test = Thing::new(); } *foo_test* ```rust - // size = 4, align = 4 + // size = 4, align = 4 let foo_test: Thing ``` "#]], @@ -1374,7 +1466,7 @@ fn y() { *x* ```rust - // size = 4, align = 4 + // size = 4, align = 4 let x: i32 ``` "#]], @@ -1505,7 +1597,7 @@ fn foo(bar:u32) { let a = id!(ba$0r); } *bar* ```rust - // size = 4, align = 4 + // size = 4, align = 4 bar: u32 ``` "#]], @@ -1524,7 +1616,7 @@ fn foo(bar:u32) { let a = id!(ba$0r); } *bar* ```rust - // size = 4, align = 4 + // size = 4, align = 4 bar: u32 ``` "#]], @@ -1760,7 +1852,7 @@ fn test_hover_function_pointer_show_identifiers() { ``` ```rust - // size = 8, align = 8, niches = 1 + // size = 8, align = 8, niches = 1 type foo = fn(a: i32, b: i32) -> i32 ``` "#]], @@ -1779,7 +1871,7 @@ fn test_hover_function_pointer_no_identifier() { ``` ```rust - // size = 8, align = 8, niches = 1 + // size = 8, align = 8, niches = 1 type foo = fn(i32, i32) -> i32 ``` "#]], @@ -1926,7 +2018,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 struct Bar ``` @@ -1963,7 +2055,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 struct Bar ``` @@ -1993,7 +2085,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 struct Bar ``` @@ -2022,7 +2114,7 @@ pub struct B$0ar ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 pub struct Bar ``` @@ -2050,7 +2142,7 @@ pub struct B$0ar ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 pub struct Bar ``` @@ -2140,7 +2232,7 @@ fn test_hover_layout_of_variant() { ``` ```rust - // size = 4, align = 2 + // size = 4, align = 2 Variant1(u8, u16) ``` "#]], @@ -2162,7 +2254,7 @@ fn test_hover_layout_of_enum() { ``` ```rust - // size = 16 (0x10), align = 8, niches = 254 + // size = 16 (0x10), align = 8, niches = 254 enum Foo { Variant1(u8, u16), Variant2(i32, u8, i64), @@ -2540,7 +2632,7 @@ fn main() { let s$0t = S{ f1:Arg(0) }; } focus_range: 7..10, name: "Arg", kind: Struct, - description: "struct Arg(u32);", + description: "struct Arg(u32)", }, }, HoverGotoTypeData { @@ -2599,7 +2691,7 @@ fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } focus_range: 7..10, name: "Arg", kind: Struct, - description: "struct Arg(u32);", + description: "struct Arg(u32)", }, }, HoverGotoTypeData { @@ -2648,7 +2740,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } focus_range: 7..8, name: "A", kind: Struct, - description: "struct A(u32);", + description: "struct A(u32)", }, }, HoverGotoTypeData { @@ -2661,7 +2753,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } focus_range: 22..23, name: "B", kind: Struct, - description: "struct B(u32);", + description: "struct B(u32)", }, }, HoverGotoTypeData { @@ -2675,7 +2767,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); } name: "C", kind: Struct, container_name: "M", - description: "pub struct C(u32);", + description: "pub struct C(u32)", }, }, ], @@ -3331,26 +3423,26 @@ struct Foo; impl Foo {} "#, expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Bar", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Bar", - kind: Struct, - description: "struct Bar", - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Bar", + kind: Struct, + description: "struct Bar", }, - ], - ), - ] - "#]], + }, + ], + ), + ] + "#]], ); } @@ -3396,26 +3488,26 @@ impl Foo { } "#, expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Foo", - kind: Struct, - description: "struct Foo", - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Foo", + kind: Struct, + description: "struct Foo", }, - ], - ), - ] - "#]], + }, + ], + ), + ] + "#]], ); } @@ -3466,7 +3558,7 @@ fn main() { *f* ```rust - // size = 8, align = 8, niches = 1 + // size = 8, align = 8, niches = 1 let f: &i32 ``` --- @@ -3476,7 +3568,7 @@ fn main() { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 f: i32 ``` "#]], @@ -3498,7 +3590,7 @@ struct S$0T(T); ``` ```rust - struct ST(T); + struct ST(T) ``` "#]], ); @@ -3519,7 +3611,7 @@ struct S$0T(T); ``` ```rust - struct ST(T); + struct ST(T) ``` "#]], ); @@ -3541,7 +3633,7 @@ struct S$0T(T); ``` ```rust - struct ST(T); + struct ST(T) ``` "#]], ); @@ -3561,7 +3653,7 @@ fn main() { *value* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let value: Const<1> ``` "#]], @@ -3582,7 +3674,7 @@ fn main() { *value* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let value: Const<0> ``` "#]], @@ -3603,7 +3695,7 @@ fn main() { *value* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let value: Const<-1> ``` "#]], @@ -3624,7 +3716,7 @@ fn main() { *value* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let value: Const ``` "#]], @@ -3645,7 +3737,7 @@ fn main() { *value* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let value: Const<'🦀'> ``` "#]], @@ -3665,7 +3757,7 @@ impl Foo { *self* ```rust - // size = 8, align = 8, niches = 1 + // size = 8, align = 8, niches = 1 self: &Foo ``` "#]], @@ -3686,7 +3778,7 @@ impl Foo { *self* ```rust - // size = 0, align = 1 + // size = 0, align = 1 self: Arc ``` "#]], @@ -4072,7 +4164,7 @@ type Fo$0o2 = Foo<2>; ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 type Foo2 = Foo<2> ``` "#]], @@ -4115,7 +4207,7 @@ enum E { ``` ```rust - // size = 1, align = 1 + // size = 1, align = 1 A = 8 ``` @@ -4141,7 +4233,7 @@ enum E { ``` ```rust - // size = 1, align = 1 + // size = 1, align = 1 A = 12 (0xC) ``` @@ -4168,7 +4260,7 @@ enum E { ``` ```rust - // size = 1, align = 1 + // size = 1, align = 1 B = 2 ``` @@ -4195,7 +4287,7 @@ enum E { ``` ```rust - // size = 1, align = 1 + // size = 1, align = 1 B = 5 ``` @@ -5002,7 +5094,7 @@ fn foo(e: E) { ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 A = 3 ``` @@ -5025,7 +5117,7 @@ fn main() { *tile4* ```rust - // size = 32 (0x20), align = 4 + // size = 32 (0x20), align = 4 let tile4: [u32; 8] ``` "#]], @@ -5262,7 +5354,7 @@ pub fn gimme() -> theitem::TheItem { ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 pub struct TheItem ``` @@ -5411,7 +5503,7 @@ mod string { ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 struct String ``` @@ -5931,26 +6023,26 @@ fn foo() { } "#, expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Foo", - kind: Struct, - description: "struct Foo", - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Foo", + kind: Struct, + description: "struct Foo", }, - ], - ), - ] - "#]], + }, + ], + ), + ] + "#]], ); } @@ -6139,7 +6231,7 @@ foo_macro!( ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 pub struct Foo ``` @@ -6165,8 +6257,8 @@ pub struct Foo(i32); ``` ```rust - // size = 4, align = 4 - pub struct Foo(i32); + // size = 4, align = 4 + pub struct Foo(i32) ``` --- @@ -6191,7 +6283,7 @@ pub struct Foo(T); ``` ```rust - pub struct Foo(T); + pub struct Foo(T) ``` --- @@ -6290,7 +6382,7 @@ enum Enum { ``` ```rust - // size = 4, align = 4 + // size = 4, align = 4 RecordV { field: u32 } ``` "#]], @@ -6313,7 +6405,7 @@ enum Enum { ``` ```rust - // size = 4, align = 4 + // size = 4, align = 4 field: u32 ``` "#]], @@ -6961,7 +7053,7 @@ fn test() { ``` ```rust - // size = 4, align = 4, offset = 0 + // size = 4, align = 4, offset = 0 f: u32 ``` "#]], @@ -6981,7 +7073,7 @@ fn test() { *s* ```rust - // size = 0, align = 1 + // size = 0, align = 1 let s: S ``` "#]], @@ -7002,7 +7094,7 @@ fn test() { *foo* ```rust - // size = 4, align = 4 + // size = 4, align = 4 let foo: i32 ``` "#]], @@ -7023,7 +7115,7 @@ format_args!("{aaaaa$0}"); *aaaaa* ```rust - // size = 16 (0x10), align = 8, niches = 1 + // size = 16 (0x10), align = 8, niches = 1 let aaaaa: &str ``` "#]], @@ -7044,7 +7136,7 @@ format_args!("{$0aaaaa}"); *aaaaa* ```rust - // size = 16 (0x10), align = 8, niches = 1 + // size = 16 (0x10), align = 8, niches = 1 let aaaaa: &str ``` "#]], @@ -7065,7 +7157,7 @@ format_args!(r"{$0aaaaa}"); *aaaaa* ```rust - // size = 16 (0x10), align = 8, niches = 1 + // size = 16 (0x10), align = 8, niches = 1 let aaaaa: &str ``` "#]], @@ -7091,7 +7183,7 @@ foo!(r"{$0aaaaa}"); *aaaaa* ```rust - // size = 16 (0x10), align = 8, niches = 1 + // size = 16 (0x10), align = 8, niches = 1 let aaaaa: &str ``` "#]], @@ -7440,8 +7532,8 @@ fn main(notable$0: u32) {} *notable* ```rust - // Implements notable traits: Notable - // size = 4, align = 4 + // Implements notable traits: Notable + // size = 4, align = 4 notable: u32 ``` "#]], @@ -7472,8 +7564,8 @@ impl Iterator for S { ``` ```rust - // Implements notable traits: Notable, Future, Iterator - // size = 0, align = 1 + // Implements notable traits: Notable, Future, Iterator + // size = 0, align = 1 struct S ``` "#]], @@ -7532,7 +7624,7 @@ extern "C" { ``` ```rust - // size = 0, align = 1 + // size = 0, align = 1 type Ty ``` "#]], @@ -7560,7 +7652,7 @@ fn main() { "#, expect![[r#" ```rust - // Implements notable traits: Notable, Future, Iterator + // Implements notable traits: Notable, Future, Iterator S ```"#]], ); diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs index 8d9ad5bda1..5ba4e514e1 100644 --- a/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/crates/ide/src/inlay_hints/implicit_drop.rs @@ -74,6 +74,10 @@ pub(super) fn hints( Ok(s) => s.value.text_range(), Err(_) => continue, }, + MirSpan::SelfParam => match source_map.self_param_syntax() { + Some(s) => s.value.text_range(), + None => continue, + }, MirSpan::Unknown => continue, }; let binding = &hir.bindings[*binding]; diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 59a7df14fd..6955e14a10 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -259,7 +259,7 @@ impl Analysis { false, CrateOrigin::Local { repo: None, name: None }, ); - change.change_file(file_id, Some(Arc::from(text))); + change.change_file(file_id, Some(text)); change.set_crate_graph(crate_graph); change.set_target_data_layouts(vec![Err("fixture has no layout".into())]); change.set_toolchains(vec![None]); diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 674ce6d52b..2123c98605 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -176,14 +176,12 @@ impl NavigationTarget { impl TryToNav for FileSymbol { fn try_to_nav(&self, db: &RootDatabase) -> Option> { - let root = db.parse_or_expand(self.loc.hir_file_id); - self.loc.ptr.to_node(&root); Some( - orig_range_with_focus( + orig_range_with_focus_r( db, self.loc.hir_file_id, - &self.loc.ptr.to_node(&root), - Some(self.loc.name_ptr.to_node(&root)), + self.loc.ptr.text_range(), + Some(self.loc.name_ptr.text_range()), ) .map(|(FileRange { file_id, range: full_range }, focus_range)| { NavigationTarget { @@ -722,7 +720,21 @@ fn orig_range_with_focus( value: &SyntaxNode, name: Option, ) -> UpmappingResult<(FileRange, Option)> { - let Some(name) = name else { return orig_range(db, hir_file, value) }; + orig_range_with_focus_r( + db, + hir_file, + value.text_range(), + name.map(|it| it.syntax().text_range()), + ) +} + +fn orig_range_with_focus_r( + db: &RootDatabase, + hir_file: HirFileId, + value: TextRange, + name: Option, +) -> UpmappingResult<(FileRange, Option)> { + let Some(name) = name else { return orig_range_r(db, hir_file, value) }; let call_kind = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap().macro_call_id).kind; @@ -733,9 +745,9 @@ fn orig_range_with_focus( .definition_range(db) }; - let value_range = InFile::new(hir_file, value).original_file_range_opt(db); + let value_range = InFile::new(hir_file, value).original_node_file_range_opt(db); let ((call_site_range, call_site_focus), def_site) = - match InFile::new(hir_file, name.syntax()).original_file_range_opt(db) { + match InFile::new(hir_file, name).original_node_file_range_opt(db) { // call site name Some((focus_range, ctxt)) if ctxt.is_root() => { // Try to upmap the node as well, if it ends up in the def site, go back to the call site @@ -802,7 +814,7 @@ fn orig_range_with_focus( } } // lost name? can't happen for single tokens - None => return orig_range(db, hir_file, value), + None => return orig_range_r(db, hir_file, value), }; UpmappingResult { @@ -840,7 +852,18 @@ fn orig_range( value: &SyntaxNode, ) -> UpmappingResult<(FileRange, Option)> { UpmappingResult { - call_site: (InFile::new(hir_file, value).original_file_range(db), None), + call_site: (InFile::new(hir_file, value).original_file_range_rooted(db), None), + def_site: None, + } +} + +fn orig_range_r( + db: &RootDatabase, + hir_file: HirFileId, + value: TextRange, +) -> UpmappingResult<(FileRange, Option)> { + UpmappingResult { + call_site: (InFile::new(hir_file, value).original_node_file_range(db).0, None), def_site: None, } } diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index dcdc6118a3..fef2aba3c6 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1710,7 +1710,7 @@ use proc_macros::mirror; mirror$0! {} "#, expect![[r#" - mirror Macro FileId(1) 1..77 22..28 + mirror ProcMacro FileId(1) 1..77 22..28 FileId(0) 26..32 "#]], diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 5fe46444ff..79324bf387 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -138,7 +138,9 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { }) { if let Some(def) = def { let file_id = match def { - Definition::Module(it) => it.declaration_source(db).map(|src| src.file_id), + Definition::Module(it) => { + it.declaration_source_range(db).map(|src| src.file_id) + } Definition::Function(it) => it.source(db).map(|src| src.file_id), _ => None, }; @@ -269,15 +271,10 @@ fn find_related_tests_in_module( Some(it) => it, _ => return, }; - let mod_source = parent_module.definition_source(sema.db); - let range = match &mod_source.value { - hir::ModuleSource::Module(m) => m.syntax().text_range(), - hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(), - hir::ModuleSource::SourceFile(f) => f.syntax().text_range(), - }; + let mod_source = parent_module.definition_source_range(sema.db); let file_id = mod_source.file_id.original_file(sema.db); - let mod_scope = SearchScope::file_range(FileRange { file_id, range }); + let mod_scope = SearchScope::file_range(FileRange { file_id, range: mod_source.value }); let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() }; find_related_tests(sema, syntax, fn_pos, Some(mod_scope), tests) } @@ -405,14 +402,15 @@ fn runnable_mod_outline_definition( let attrs = def.attrs(sema.db); let cfg = attrs.cfg(); - match def.definition_source(sema.db).value { - hir::ModuleSource::SourceFile(_) => Some(Runnable { + if def.as_source_file_id(sema.db).is_some() { + Some(Runnable { use_name_in_title: false, nav: def.to_nav(sema.db).call_site(), kind: RunnableKind::TestMod { path }, cfg, - }), - _ => None, + }) + } else { + None } } diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index d2bd3bab14..6aaf8f8e77 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -248,6 +248,7 @@ fn traverse( // an attribute nested in a macro call will not emit `inside_attribute` let mut inside_attribute = false; let mut inside_macro_call = false; + let mut inside_proc_macro_call = false; // Walk all nodes, keeping track of whether we are inside a macro or not. // If in macro, expand it first and highlight the expanded code. @@ -298,8 +299,9 @@ fn traverse( ast::Item::Fn(_) | ast::Item::Const(_) | ast::Item::Static(_) => { bindings_shadow_count.clear() } - ast::Item::MacroCall(_) => { + ast::Item::MacroCall(ref macro_call) => { inside_macro_call = true; + inside_proc_macro_call = sema.is_proc_macro_call(macro_call); } _ => (), } @@ -344,6 +346,7 @@ fn traverse( } Some(ast::Item::MacroCall(_)) => { inside_macro_call = false; + inside_proc_macro_call = false; } _ => (), } @@ -519,6 +522,9 @@ fn traverse( highlight |= HlMod::Attribute } if inside_macro_call && tt_level > 0 { + if inside_proc_macro_call { + highlight |= HlMod::ProcMacro + } highlight |= HlMod::Macro } diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index bbc6b55a64..a6dca0541e 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs @@ -98,6 +98,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 6d4cdd0efe..5163a0de41 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs @@ -75,6 +75,7 @@ pub enum HlMod { Library, /// Used to differentiate individual elements within macro calls. Macro, + ProcMacro, /// Mutable binding. Mutable, /// Used for public items. @@ -146,6 +147,7 @@ impl HlTag { SymbolKind::LifetimeParam => "lifetime", SymbolKind::Local => "variable", SymbolKind::Macro => "macro", + SymbolKind::ProcMacro => "proc_macro", SymbolKind::Module => "module", SymbolKind::SelfParam => "self_keyword", SymbolKind::SelfType => "self_type_keyword", @@ -219,6 +221,7 @@ impl HlMod { HlMod::IntraDocLink, HlMod::Library, HlMod::Macro, + HlMod::ProcMacro, HlMod::Mutable, HlMod::Public, HlMod::Reference, @@ -243,6 +246,7 @@ impl HlMod { HlMod::IntraDocLink => "intra_doc_link", HlMod::Library => "library", HlMod::Macro => "macro", + HlMod::ProcMacro => "proc_macro", HlMod::Mutable => "mutable", HlMod::Public => "public", HlMod::Reference => "reference", diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html index 4dcbfe4eb6..6994cb3d5c 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html index bf5505caf3..dc2d103b58 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html index 977d18c6b7..093cc2358a 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html index 0d1b3c1f18..154b823fff 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html index dd1528ed03..58613bf151 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index d5f92aa5d4..34274932af 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index 88a008796b..729e4791f5 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html index bdeb09d2f8..066fcfb1df 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index f9c33b8a60..58a147dd80 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html index 2043752bc7..22ae5c82a4 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html index ec39998de2..af77999659 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index 4063cf9f75..32ac6a94d8 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -29,6 +29,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .numeric_literal { color: #BFEBBF; } .bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } +.proc_macro { color: #94BFF3; text-decoration: underline; } .derive { color: #94BFF3; font-style: italic; } .module { color: #AFD8AF; } .value_param { color: #DCDCCC; } @@ -45,12 +46,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
use proc_macros::{mirror, identity, DeriveIdentity};
 
-mirror! {
-    {
-        ,i32 :x pub
-        ,i32 :y pub
-    } Foo struct
-}
+mirror! {
+    {
+        ,i32 :x pub
+        ,i32 :y pub
+    } Foo struct
+}
 macro_rules! def_fn {
     ($($tt:tt)*) => {$($tt)*}
 }
@@ -93,7 +94,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 }
 
 
-include!(concat!("foo/", "foo.rs"));
+include!(concat!("foo/", "foo.rs"));
 
 fn main() {
     format_args!("Hello, {}!", (92,).0);
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
index 4dcf8e5f01..ef8a48ca1c 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
index 084bbf2f74..a2ded15fd1 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
index 1af4bcfbd9..d123ee0497 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
index 7ee7b338c1..4429e5d933 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 84a823363f..b6458fa7ca 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index c72ea54e94..49b588baa5 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -29,6 +29,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
 .derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
diff --git a/crates/ide/src/test_explorer.rs b/crates/ide/src/test_explorer.rs
index 2e741021ea..ca47139970 100644
--- a/crates/ide/src/test_explorer.rs
+++ b/crates/ide/src/test_explorer.rs
@@ -11,7 +11,7 @@ use crate::{navigation_target::ToNav, runnables::runnable_fn, Runnable, TryToNav
 
 #[derive(Debug)]
 pub enum TestItemKind {
-    Crate,
+    Crate(CrateId),
     Module,
     Function,
 }
@@ -32,15 +32,17 @@ pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec {
     crate_graph
         .iter()
         .filter(|&id| crate_graph[id].origin.is_local())
-        .filter_map(|id| Some(crate_graph[id].display_name.as_ref()?.to_string()))
-        .map(|id| TestItem {
-            kind: TestItemKind::Crate,
-            label: id.clone(),
-            id,
-            parent: None,
-            file: None,
-            text_range: None,
-            runnable: None,
+        .filter_map(|id| {
+            let test_id = crate_graph[id].display_name.as_ref()?.to_string();
+            Some(TestItem {
+                kind: TestItemKind::Crate(id),
+                label: test_id.clone(),
+                id: test_id,
+                parent: None,
+                file: None,
+                text_range: None,
+                runnable: None,
+            })
         })
         .collect()
 }
@@ -118,12 +120,13 @@ pub(crate) fn discover_tests_in_crate(db: &RootDatabase, crate_id: CrateId) -> V
     let Some(crate_test_id) = &crate_graph[crate_id].display_name else {
         return vec![];
     };
+    let kind = TestItemKind::Crate(crate_id);
     let crate_test_id = crate_test_id.to_string();
     let crate_id: Crate = crate_id.into();
     let module = crate_id.root_module();
     let mut r = vec![TestItem {
         id: crate_test_id.clone(),
-        kind: TestItemKind::Crate,
+        kind,
         label: crate_test_id.clone(),
         parent: None,
         file: None,
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index a1c089520d..fe680e47fe 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -361,8 +361,8 @@ fn load_crate_graph(
     let changes = vfs.take_changes();
     for file in changes {
         if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
-            if let Ok(text) = std::str::from_utf8(&v) {
-                analysis_change.change_file(file.file_id, Some(text.into()))
+            if let Ok(text) = String::from_utf8(v) {
+                analysis_change.change_file(file.file_id, Some(text))
             }
         }
     }
@@ -419,14 +419,10 @@ impl ProcMacroExpander for Expander {
 #[cfg(test)]
 mod tests {
     use ide_db::base_db::SourceDatabase;
+    use vfs::file_set::FileSetConfigBuilder;
 
     use super::*;
 
-    use ide_db::base_db::SourceRootId;
-    use vfs::{file_set::FileSetConfigBuilder, VfsPath};
-
-    use crate::SourceRootConfig;
-
     #[test]
     fn test_loading_rust_analyzer() {
         let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index d946ecc1ca..ac7f071178 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -1,6 +1,7 @@
 //! This module add real world mbe example for benchmark tests
 
 use rustc_hash::FxHashMap;
+use span::Span;
 use syntax::{
     ast::{self, HasName},
     AstNode, SmolStr,
@@ -9,7 +10,7 @@ use test_utils::{bench, bench_fixture, skip_slow_tests};
 
 use crate::{
     parser::{MetaVarKind, Op, RepeatKind, Separator},
-    syntax_node_to_token_tree, DeclarativeMacro, DummyTestSpanData, DummyTestSpanMap, DUMMY,
+    syntax_node_to_token_tree, DeclarativeMacro, DummyTestSpanMap, DUMMY,
 };
 
 #[test]
@@ -50,14 +51,14 @@ fn benchmark_expand_macro_rules() {
     assert_eq!(hash, 69413);
 }
 
-fn macro_rules_fixtures() -> FxHashMap> {
+fn macro_rules_fixtures() -> FxHashMap> {
     macro_rules_fixtures_tt()
         .into_iter()
         .map(|(id, tt)| (id, DeclarativeMacro::parse_macro_rules(&tt, true, true)))
         .collect()
 }
 
-fn macro_rules_fixtures_tt() -> FxHashMap> {
+fn macro_rules_fixtures_tt() -> FxHashMap> {
     let fixture = bench_fixture::numerous_macro_rules();
     let source_file = ast::SourceFile::parse(&fixture).ok().unwrap();
 
@@ -79,8 +80,8 @@ fn macro_rules_fixtures_tt() -> FxHashMap
 
 /// Generate random invocation fixtures from rules
 fn invocation_fixtures(
-    rules: &FxHashMap>,
-) -> Vec<(String, tt::Subtree)> {
+    rules: &FxHashMap>,
+) -> Vec<(String, tt::Subtree)> {
     let mut seed = 123456789;
     let mut res = Vec::new();
 
@@ -128,8 +129,8 @@ fn invocation_fixtures(
     return res;
 
     fn collect_from_op(
-        op: &Op,
-        token_trees: &mut Vec>,
+        op: &Op,
+        token_trees: &mut Vec>,
         seed: &mut usize,
     ) {
         return match op {
@@ -221,19 +222,19 @@ fn invocation_fixtures(
             *seed = usize::wrapping_add(usize::wrapping_mul(*seed, a), c);
             *seed
         }
-        fn make_ident(ident: &str) -> tt::TokenTree {
+        fn make_ident(ident: &str) -> tt::TokenTree {
             tt::Leaf::Ident(tt::Ident { span: DUMMY, text: SmolStr::new(ident) }).into()
         }
-        fn make_punct(char: char) -> tt::TokenTree {
+        fn make_punct(char: char) -> tt::TokenTree {
             tt::Leaf::Punct(tt::Punct { span: DUMMY, char, spacing: tt::Spacing::Alone }).into()
         }
-        fn make_literal(lit: &str) -> tt::TokenTree {
+        fn make_literal(lit: &str) -> tt::TokenTree {
             tt::Leaf::Literal(tt::Literal { span: DUMMY, text: SmolStr::new(lit) }).into()
         }
         fn make_subtree(
             kind: tt::DelimiterKind,
-            token_trees: Option>>,
-        ) -> tt::TokenTree {
+            token_trees: Option>>,
+        ) -> tt::TokenTree {
             tt::Subtree {
                 delimiter: tt::Delimiter { open: DUMMY, close: DUMMY, kind },
                 token_trees: token_trees.map(Vec::into_boxed_slice).unwrap_or_default(),
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index 3c270e30a9..57d6082dd7 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -23,8 +23,11 @@ pub trait SpanMapper {
     fn span_for(&self, range: TextRange) -> S;
 }
 
-impl SpanMapper for SpanMap {
-    fn span_for(&self, range: TextRange) -> S {
+impl SpanMapper> for SpanMap
+where
+    SpanData: Span,
+{
+    fn span_for(&self, range: TextRange) -> SpanData {
         self.span_at(range.start())
     }
 }
@@ -38,32 +41,30 @@ impl> SpanMapper for &SM {
 /// Dummy things for testing where spans don't matter.
 pub(crate) mod dummy_test_span_utils {
 
+    use span::{Span, SyntaxContextId};
+
     use super::*;
 
-    pub type DummyTestSpanData = span::SpanData;
-    pub const DUMMY: DummyTestSpanData = span::SpanData {
+    pub const DUMMY: Span = Span {
         range: TextRange::empty(TextSize::new(0)),
         anchor: span::SpanAnchor {
             file_id: span::FileId::BOGUS,
             ast_id: span::ROOT_ERASED_FILE_AST_ID,
         },
-        ctx: DummyTestSyntaxContext,
+        ctx: SyntaxContextId::ROOT,
     };
 
-    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-    pub struct DummyTestSyntaxContext;
-
     pub struct DummyTestSpanMap;
 
-    impl SpanMapper> for DummyTestSpanMap {
-        fn span_for(&self, range: syntax::TextRange) -> span::SpanData {
-            span::SpanData {
+    impl SpanMapper for DummyTestSpanMap {
+        fn span_for(&self, range: syntax::TextRange) -> Span {
+            Span {
                 range,
                 anchor: span::SpanAnchor {
                     file_id: span::FileId::BOGUS,
                     ast_id: span::ROOT_ERASED_FILE_AST_ID,
                 },
-                ctx: DummyTestSyntaxContext,
+                ctx: SyntaxContextId::ROOT,
             }
         }
     }
@@ -92,7 +93,7 @@ pub fn syntax_node_to_token_tree_modified(
     node: &SyntaxNode,
     map: SpanMap,
     append: FxHashMap>>>,
-    remove: FxHashSet,
+    remove: FxHashSet,
     call_site: SpanData,
 ) -> tt::Subtree>
 where
@@ -121,7 +122,7 @@ where
 pub fn token_tree_to_syntax_node(
     tt: &tt::Subtree>,
     entry_point: parser::TopEntryPoint,
-) -> (Parse, SpanMap>)
+) -> (Parse, SpanMap)
 where
     SpanData: Span,
     Ctx: Copy,
@@ -629,7 +630,7 @@ struct Converter {
     /// Used to make the emitted text ranges in the spans relative to the span anchor.
     map: SpanMap,
     append: FxHashMap>>,
-    remove: FxHashSet,
+    remove: FxHashSet,
     call_site: S,
 }
 
@@ -638,7 +639,7 @@ impl Converter {
         node: &SyntaxNode,
         map: SpanMap,
         append: FxHashMap>>,
-        remove: FxHashSet,
+        remove: FxHashSet,
         call_site: S,
     ) -> Self {
         let mut this = Converter {
@@ -660,16 +661,25 @@ impl Converter {
     fn next_token(&mut self) -> Option {
         while let Some(ev) = self.preorder.next() {
             match ev {
-                WalkEvent::Enter(SyntaxElement::Token(t)) => return Some(t),
-                WalkEvent::Enter(SyntaxElement::Node(n)) if self.remove.contains(&n) => {
-                    self.preorder.skip_subtree();
-                    if let Some(mut v) = self.append.remove(&n.into()) {
-                        v.reverse();
-                        self.current_leaves.extend(v);
-                        return None;
+                WalkEvent::Enter(token) => {
+                    if self.remove.contains(&token) {
+                        match token {
+                            syntax::NodeOrToken::Token(_) => {
+                                continue;
+                            }
+                            node => {
+                                self.preorder.skip_subtree();
+                                if let Some(mut v) = self.append.remove(&node) {
+                                    v.reverse();
+                                    self.current_leaves.extend(v);
+                                    return None;
+                                }
+                            }
+                        }
+                    } else if let syntax::NodeOrToken::Token(token) = token {
+                        return Some(token);
                     }
                 }
-                WalkEvent::Enter(SyntaxElement::Node(_)) => (),
                 WalkEvent::Leave(ele) => {
                     if let Some(mut v) = self.append.remove(&ele) {
                         v.reverse();
@@ -824,7 +834,7 @@ where
     cursor: Cursor<'a, SpanData>,
     text_pos: TextSize,
     inner: SyntaxTreeBuilder,
-    token_map: SpanMap>,
+    token_map: SpanMap,
 }
 
 impl<'a, Ctx> TtTreeSink<'a, Ctx>
@@ -841,7 +851,7 @@ where
         }
     }
 
-    fn finish(mut self) -> (Parse, SpanMap>) {
+    fn finish(mut self) -> (Parse, SpanMap) {
         self.token_map.finish();
         (self.inner.finish(), self.token_map)
     }
diff --git a/crates/mbe/src/syntax_bridge/tests.rs b/crates/mbe/src/syntax_bridge/tests.rs
index 11d1a72879..a261b1d431 100644
--- a/crates/mbe/src/syntax_bridge/tests.rs
+++ b/crates/mbe/src/syntax_bridge/tests.rs
@@ -1,4 +1,5 @@
 use rustc_hash::FxHashMap;
+use span::Span;
 use syntax::{ast, AstNode};
 use test_utils::extract_annotations;
 use tt::{
@@ -6,7 +7,7 @@ use tt::{
     Leaf, Punct, Spacing,
 };
 
-use crate::{syntax_node_to_token_tree, DummyTestSpanData, DummyTestSpanMap, DUMMY};
+use crate::{syntax_node_to_token_tree, DummyTestSpanMap, DUMMY};
 
 fn check_punct_spacing(fixture: &str) {
     let source_file = ast::SourceFile::parse(fixture).ok().unwrap();
@@ -28,7 +29,7 @@ fn check_punct_spacing(fixture: &str) {
     while !cursor.eof() {
         while let Some(token_tree) = cursor.token_tree() {
             if let TokenTreeRef::Leaf(
-                Leaf::Punct(Punct { spacing, span: DummyTestSpanData { range, .. }, .. }),
+                Leaf::Punct(Punct { spacing, span: Span { range, .. }, .. }),
                 _,
             ) = token_tree
             {
diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs
index 54a20357d2..11b008fc0b 100644
--- a/crates/proc-macro-srv/src/tests/mod.rs
+++ b/crates/proc-macro-srv/src/tests/mod.rs
@@ -8,7 +8,12 @@ use expect_test::expect;
 
 #[test]
 fn test_derive_empty() {
-    assert_expand("DeriveEmpty", r#"struct S;"#, expect!["SUBTREE $$ 1 1"], expect!["SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"]);
+    assert_expand(
+        "DeriveEmpty",
+        r#"struct S;"#,
+        expect!["SUBTREE $$ 1 1"],
+        expect!["SUBTREE $$ 42:2@0..100#0 42:2@0..100#0"],
+    );
 }
 
 #[test]
@@ -21,15 +26,15 @@ fn test_derive_error() {
               IDENT   compile_error 1
               PUNCH   ! [alone] 1
               SUBTREE () 1 1
-                LITERAL "#[derive(DeriveError)] struct S ;" 1
+                LITERAL "#[derive(DeriveError)] struct S ;"1
               PUNCH   ; [alone] 1"##]],
         expect![[r##"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-                LITERAL "#[derive(DeriveError)] struct S ;" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   compile_error 42:2@0..100#0
+              PUNCH   ! [alone] 42:2@0..100#0
+              SUBTREE () 42:2@0..100#0 42:2@0..100#0
+                LITERAL "#[derive(DeriveError)] struct S ;"42:2@0..100#0
+              PUNCH   ; [alone] 42:2@0..100#0"##]],
     );
 }
 
@@ -42,20 +47,20 @@ fn test_fn_like_macro_noop() {
             SUBTREE $$ 1 1
               IDENT   ident 1
               PUNCH   , [alone] 1
-              LITERAL 0 1
+              LITERAL 01
               PUNCH   , [alone] 1
-              LITERAL 1 1
+              LITERAL 11
               PUNCH   , [alone] 1
               SUBTREE [] 1 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 0 SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 8..9, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 1 SpanData { range: 10..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              SUBTREE [] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 14..15, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   ident 42:2@0..5#0
+              PUNCH   , [alone] 42:2@5..6#0
+              LITERAL 042:2@7..8#0
+              PUNCH   , [alone] 42:2@8..9#0
+              LITERAL 142:2@10..11#0
+              PUNCH   , [alone] 42:2@11..12#0
+              SUBTREE [] 42:2@13..14#0 42:2@14..15#0"#]],
     );
 }
 
@@ -70,10 +75,10 @@ fn test_fn_like_macro_clone_ident_subtree() {
               PUNCH   , [alone] 1
               SUBTREE [] 1 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              SUBTREE [] SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   ident 42:2@0..5#0
+              PUNCH   , [alone] 42:2@5..6#0
+              SUBTREE [] 42:2@7..8#0 42:2@7..8#0"#]],
     );
 }
 
@@ -86,8 +91,8 @@ fn test_fn_like_macro_clone_raw_ident() {
             SUBTREE $$ 1 1
               IDENT   r#async 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   r#async SpanData { range: 0..7, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   r#async 42:2@0..7#0"#]],
     );
 }
 
@@ -100,8 +105,8 @@ fn test_fn_like_fn_like_span_join() {
             SUBTREE $$ 1 1
               IDENT   r#joined 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   r#joined SpanData { range: 0..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   r#joined 42:2@0..11#0"#]],
     );
 }
 
@@ -116,10 +121,10 @@ fn test_fn_like_fn_like_span_ops() {
               IDENT   resolved_at_def_site 1
               IDENT   start_span 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   set_def_site SpanData { range: 0..150, anchor: SpanAnchor(FileId(41), 1), ctx: SyntaxContextId(0) }
-              IDENT   resolved_at_def_site SpanData { range: 13..33, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   start_span SpanData { range: 34..34, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   set_def_site 41:1@0..150#0
+              IDENT   resolved_at_def_site 42:2@13..33#0
+              IDENT   start_span 42:2@34..34#0"#]],
     );
 }
 
@@ -130,22 +135,22 @@ fn test_fn_like_mk_literals() {
         r#""#,
         expect![[r#"
             SUBTREE $$ 1 1
-              LITERAL b"byte_string" 1
-              LITERAL 'c' 1
-              LITERAL "string" 1
-              LITERAL 3.14f64 1
-              LITERAL 3.14 1
-              LITERAL 123i64 1
-              LITERAL 123 1"#]],
+              LITERAL b"byte_string"1
+              LITERAL 'c'1
+              LITERAL "string"1
+              LITERAL 3.14f641
+              LITERAL 3.141
+              LITERAL 123i641
+              LITERAL 1231"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL b"byte_string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 'c' SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL "string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 3.14f64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 3.14 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 123i64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 123 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              LITERAL b"byte_string"42:2@0..100#0
+              LITERAL 'c'42:2@0..100#0
+              LITERAL "string"42:2@0..100#0
+              LITERAL 3.14f6442:2@0..100#0
+              LITERAL 3.1442:2@0..100#0
+              LITERAL 123i6442:2@0..100#0
+              LITERAL 12342:2@0..100#0"#]],
     );
 }
 
@@ -159,9 +164,9 @@ fn test_fn_like_mk_idents() {
               IDENT   standard 1
               IDENT   r#raw 1"#]],
         expect![[r#"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   standard SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   r#raw SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   standard 42:2@0..100#0
+              IDENT   r#raw 42:2@0..100#0"#]],
     );
 }
 
@@ -172,48 +177,48 @@ fn test_fn_like_macro_clone_literals() {
         r###"1u16, 2_u32, -4i64, 3.14f32, "hello bridge", "suffixed"suffix, r##"raw"##, 'a', b'b', c"null""###,
         expect![[r###"
             SUBTREE $$ 1 1
-              LITERAL 1u16 1
+              LITERAL 1u161
               PUNCH   , [alone] 1
-              LITERAL 2_u32 1
+              LITERAL 2_u321
               PUNCH   , [alone] 1
               PUNCH   - [alone] 1
-              LITERAL 4i64 1
+              LITERAL 4i641
               PUNCH   , [alone] 1
-              LITERAL 3.14f32 1
+              LITERAL 3.14f321
               PUNCH   , [alone] 1
-              LITERAL "hello bridge" 1
+              LITERAL "hello bridge"1
               PUNCH   , [alone] 1
-              LITERAL "suffixed"suffix 1
+              LITERAL "suffixed"suffix1
               PUNCH   , [alone] 1
-              LITERAL r##"raw"## 1
+              LITERAL r##"raw"##1
               PUNCH   , [alone] 1
-              LITERAL 'a' 1
+              LITERAL 'a'1
               PUNCH   , [alone] 1
-              LITERAL b'b' 1
+              LITERAL b'b'1
               PUNCH   , [alone] 1
-              LITERAL c"null" 1"###]],
+              LITERAL c"null"1"###]],
         expect![[r###"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 1u16 SpanData { range: 0..4, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 4..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 2_u32 SpanData { range: 6..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   - [alone] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 4i64 SpanData { range: 14..18, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 18..19, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 3.14f32 SpanData { range: 20..27, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 27..28, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL "hello bridge" SpanData { range: 29..43, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 43..44, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL "suffixed"suffix SpanData { range: 45..61, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 61..62, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL r##"raw"## SpanData { range: 63..73, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 73..74, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL 'a' SpanData { range: 75..78, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 78..79, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL b'b' SpanData { range: 80..84, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   , [alone] SpanData { range: 84..85, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              LITERAL c"null" SpanData { range: 86..93, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"###]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              LITERAL 1u1642:2@0..4#0
+              PUNCH   , [alone] 42:2@4..5#0
+              LITERAL 2_u3242:2@6..11#0
+              PUNCH   , [alone] 42:2@11..12#0
+              PUNCH   - [alone] 42:2@13..14#0
+              LITERAL 4i6442:2@14..18#0
+              PUNCH   , [alone] 42:2@18..19#0
+              LITERAL 3.14f3242:2@20..27#0
+              PUNCH   , [alone] 42:2@27..28#0
+              LITERAL "hello bridge"42:2@29..43#0
+              PUNCH   , [alone] 42:2@43..44#0
+              LITERAL "suffixed"suffix42:2@45..61#0
+              PUNCH   , [alone] 42:2@61..62#0
+              LITERAL r##"raw"##42:2@63..73#0
+              PUNCH   , [alone] 42:2@73..74#0
+              LITERAL 'a'42:2@75..78#0
+              PUNCH   , [alone] 42:2@78..79#0
+              LITERAL b'b'42:2@80..84#0
+              PUNCH   , [alone] 42:2@84..85#0
+              LITERAL c"null"42:2@86..93#0"###]],
     );
 }
 
@@ -231,15 +236,15 @@ fn test_attr_macro() {
               IDENT   compile_error 1
               PUNCH   ! [alone] 1
               SUBTREE () 1 1
-                LITERAL "#[attr_error(some arguments)] mod m {}" 1
+                LITERAL "#[attr_error(some arguments)] mod m {}"1
               PUNCH   ; [alone] 1"##]],
         expect![[r##"
-            SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              IDENT   compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-                LITERAL "#[attr_error(some arguments)] mod m {}" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }
-              PUNCH   ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]],
+            SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
+              IDENT   compile_error 42:2@0..100#0
+              PUNCH   ! [alone] 42:2@0..100#0
+              SUBTREE () 42:2@0..100#0 42:2@0..100#0
+                LITERAL "#[attr_error(some arguments)] mod m {}"42:2@0..100#0
+              PUNCH   ; [alone] 42:2@0..100#0"##]],
     );
 }
 
diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs
index 9a1311d955..6050bc9e36 100644
--- a/crates/proc-macro-srv/src/tests/utils.rs
+++ b/crates/proc-macro-srv/src/tests/utils.rs
@@ -91,7 +91,7 @@ fn assert_expand_impl(
     let res = expander
         .expand(macro_name, fixture.into_subtree(call_site), attr, def_site, call_site, mixed_site)
         .unwrap();
-    expect_s.assert_eq(&format!("{res:?}"));
+    expect_s.assert_eq(&format!("{res:#?}"));
 }
 
 pub(crate) fn list() -> Vec {
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 766606be7b..e6984d6f41 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -42,6 +42,7 @@ triomphe.workspace = true
 nohash-hasher.workspace = true
 always-assert = "0.2.0"
 walkdir = "2.3.2"
+semver.workspace = true
 
 cfg.workspace = true
 flycheck.workspace = true
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 07e04a8366..e747ec87b1 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -16,6 +16,7 @@ use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc};
 use anyhow::Context;
 use lsp_server::Connection;
 use rust_analyzer::{cli::flags, config::Config, from_json};
+use semver::Version;
 use tracing_subscriber::fmt::writer::BoxMakeWriter;
 use vfs::AbsPathBuf;
 
@@ -193,10 +194,18 @@ fn run_server() -> anyhow::Result<()> {
         }
     };
 
-    let mut is_visual_studio_code = false;
+    let mut visual_studio_code_version = None;
     if let Some(client_info) = client_info {
-        tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
-        is_visual_studio_code = client_info.name.starts_with("Visual Studio Code");
+        tracing::info!(
+            "Client '{}' {}",
+            client_info.name,
+            client_info.version.as_deref().unwrap_or_default()
+        );
+        visual_studio_code_version = client_info
+            .name
+            .starts_with("Visual Studio Code")
+            .then(|| client_info.version.as_deref().map(Version::parse).and_then(Result::ok))
+            .flatten();
     }
 
     let workspace_roots = workspace_folders
@@ -210,7 +219,8 @@ fn run_server() -> anyhow::Result<()> {
         })
         .filter(|workspaces| !workspaces.is_empty())
         .unwrap_or_else(|| vec![root_path.clone()]);
-    let mut config = Config::new(root_path, capabilities, workspace_roots, is_visual_studio_code);
+    let mut config =
+        Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version);
     if let Some(json) = initialization_options {
         if let Err(e) = config.update(json) {
             use lsp_types::{
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index ef184032bf..5c474908e7 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -1053,7 +1053,7 @@ fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id:
     };
     let root = db.parse_or_expand(src.file_id);
     let node = src.map(|e| e.to_node(&root).syntax().clone());
-    let original_range = node.as_ref().original_file_range(db);
+    let original_range = node.as_ref().original_file_range_rooted(db);
     let path = vfs.file_path(original_range.file_id);
     let line_index = db.line_index(original_range.file_id);
     let text_range = original_range.range;
@@ -1069,7 +1069,7 @@ fn location_csv_pat(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, pat_id: Pa
     };
     let root = db.parse_or_expand(src.file_id);
     let node = src.map(|e| e.to_node(&root).syntax().clone());
-    let original_range = node.as_ref().original_file_range(db);
+    let original_range = node.as_ref().original_file_range_rooted(db);
     let path = vfs.file_path(original_range.file_id);
     let line_index = db.line_index(original_range.file_id);
     let text_range = original_range.range;
@@ -1088,7 +1088,7 @@ fn expr_syntax_range<'a>(
     if let Ok(src) = src {
         let root = db.parse_or_expand(src.file_id);
         let node = src.map(|e| e.to_node(&root).syntax().clone());
-        let original_range = node.as_ref().original_file_range(db);
+        let original_range = node.as_ref().original_file_range_rooted(db);
         let path = vfs.file_path(original_range.file_id);
         let line_index = db.line_index(original_range.file_id);
         let text_range = original_range.range;
@@ -1109,7 +1109,7 @@ fn pat_syntax_range<'a>(
     if let Ok(src) = src {
         let root = db.parse_or_expand(src.file_id);
         let node = src.map(|e| e.to_node(&root).syntax().clone());
-        let original_range = node.as_ref().original_file_range(db);
+        let original_range = node.as_ref().original_file_range_rooted(db);
         let path = vfs.file_path(original_range.file_id);
         let line_index = db.line_index(original_range.file_id);
         let text_range = original_range.range;
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index 7ad87ab97f..84f2e60087 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -134,7 +134,7 @@ impl Tester {
         let should_have_no_error = text.contains("// check-pass")
             || text.contains("// build-pass")
             || text.contains("// run-pass");
-        change.change_file(self.root_file, Some(Arc::from(text)));
+        change.change_file(self.root_file, Some(text));
         self.host.apply_change(change);
         let diagnostic_config = DiagnosticsConfig::test_sample();
 
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 8fd59d159c..1061a433a5 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -32,8 +32,8 @@ impl flags::Scip {
         let mut config = crate::config::Config::new(
             root.clone(),
             lsp_types::ClientCapabilities::default(),
-            /* workspace_roots = */ vec![],
-            /* is_visual_studio_code = */ false,
+            vec![],
+            None,
         );
 
         if let Some(p) = self.config_path {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 9e81c8dd66..cbf1524659 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -31,6 +31,7 @@ use project_model::{
     CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
 };
 use rustc_hash::{FxHashMap, FxHashSet};
+use semver::Version;
 use serde::{de::DeserializeOwned, Deserialize};
 use stdx::format_to_acc;
 use vfs::{AbsPath, AbsPathBuf};
@@ -624,7 +625,7 @@ pub struct Config {
     data: ConfigData,
     detached_files: Vec,
     snippets: Vec,
-    is_visual_studio_code: bool,
+    visual_studio_code_version: Option,
 }
 
 type ParallelCachePrimingNumThreads = u8;
@@ -823,7 +824,7 @@ impl Config {
         root_path: AbsPathBuf,
         caps: ClientCapabilities,
         workspace_roots: Vec,
-        is_visual_studio_code: bool,
+        visual_studio_code_version: Option,
     ) -> Self {
         Config {
             caps,
@@ -833,7 +834,7 @@ impl Config {
             root_path,
             snippets: Default::default(),
             workspace_roots,
-            is_visual_studio_code,
+            visual_studio_code_version,
         }
     }
 
@@ -1778,10 +1779,10 @@ impl Config {
         self.data.typing_autoClosingAngleBrackets_enable
     }
 
-    // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124
-    // hence, distinguish it for now.
-    pub fn is_visual_studio_code(&self) -> bool {
-        self.is_visual_studio_code
+    // VSCode is our reference implementation, so we allow ourselves to work around issues by
+    // special casing certain versions
+    pub fn visual_studio_code_version(&self) -> Option<&Version> {
+        self.visual_studio_code_version.as_ref()
     }
 }
 // Deserialization definitions
@@ -2694,7 +2695,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
@@ -2710,7 +2711,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
@@ -2726,7 +2727,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
@@ -2745,7 +2746,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
@@ -2764,7 +2765,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
@@ -2783,7 +2784,7 @@ mod tests {
             AbsPathBuf::try_from(project_root()).unwrap(),
             Default::default(),
             vec![],
-            false,
+            None,
         );
         config
             .update(serde_json::json!({
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index e900f2601d..6e6cc53c25 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -542,7 +542,7 @@ mod tests {
                 workspace_root.to_path_buf(),
                 ClientCapabilities::default(),
                 Vec::new(),
-                false,
+                None,
             ),
         );
         let snap = state.snapshot();
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 0e560e54ed..1b4c33d858 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -330,7 +330,7 @@ impl GlobalState {
                         // FIXME: Consider doing normalization in the `vfs` instead? That allows
                         // getting rid of some locking
                         let (text, line_endings) = LineEndings::normalize(text);
-                        (Arc::from(text), line_endings)
+                        (text, line_endings)
                     })
                 } else {
                     None
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 3bba4847f9..1c5a862c70 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -20,7 +20,6 @@ use ide_db::{
 };
 use project_model::CargoConfig;
 use test_utils::project_root;
-use triomphe::Arc;
 use vfs::{AbsPathBuf, VfsPath};
 
 use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
@@ -57,8 +56,6 @@ fn integrated_highlighting_benchmark() {
         vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
     };
 
-    let _g = crate::tracing::hprof::init("*>150");
-
     {
         let _it = stdx::timeit("initial");
         let analysis = host.analysis();
@@ -68,13 +65,16 @@ fn integrated_highlighting_benchmark() {
     {
         let _it = stdx::timeit("change");
         let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        text.push_str("\npub fn _dummy() {}\n");
+        text = text.replace(
+            "self.data.cargo_buildScripts_rebuildOnSave",
+            "self. data. cargo_buildScripts_rebuildOnSave",
+        );
         let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
+        change.change_file(file_id, Some(text));
         host.apply_change(change);
     }
 
-    let _g = crate::tracing::hprof::init("*>50");
+    let _g = crate::tracing::hprof::init("*>20");
 
     {
         let _it = stdx::timeit("after change");
@@ -125,7 +125,7 @@ fn integrated_completion_benchmark() {
             patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
                 + "sel".len();
         let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
+        change.change_file(file_id, Some(text));
         host.apply_change(change);
         completion_offset
     };
@@ -168,7 +168,7 @@ fn integrated_completion_benchmark() {
             patch(&mut text, "sel;\ndb.struct_data(self.id)", ";sel;\ndb.struct_data(self.id)")
                 + ";sel".len();
         let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
+        change.change_file(file_id, Some(text));
         host.apply_change(change);
         completion_offset
     };
@@ -210,7 +210,7 @@ fn integrated_completion_benchmark() {
             patch(&mut text, "sel;\ndb.struct_data(self.id)", "self.;\ndb.struct_data(self.id)")
                 + "self.".len();
         let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
+        change.change_file(file_id, Some(text));
         host.apply_change(change);
         completion_offset
     };
@@ -307,7 +307,7 @@ fn integrated_diagnostics_benchmark() {
         let mut text = host.analysis().file_text(file_id).unwrap().to_string();
         patch(&mut text, "db.struct_data(self.id)", "();\ndb.struct_data(self.id)");
         let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
+        change.change_file(file_id, Some(text));
         host.apply_change(change);
     };
 
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index 86ab652f8e..710ce7f8ac 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -234,6 +234,13 @@ impl Notification for EndRunTest {
     const METHOD: &'static str = "experimental/endRunTest";
 }
 
+pub enum AppendOutputToRunTest {}
+
+impl Notification for AppendOutputToRunTest {
+    type Params = String;
+    const METHOD: &'static str = "experimental/appendOutputToRunTest";
+}
+
 pub enum AbortRunTest {}
 
 impl Notification for AbortRunTest {
diff --git a/crates/rust-analyzer/src/lsp/semantic_tokens.rs b/crates/rust-analyzer/src/lsp/semantic_tokens.rs
index dd7dcf5277..c5081c4bea 100644
--- a/crates/rust-analyzer/src/lsp/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/lsp/semantic_tokens.rs
@@ -85,6 +85,7 @@ define_semantic_token_types![
         (LIFETIME, "lifetime"),
         (LOGICAL, "logical") => OPERATOR,
         (MACRO_BANG, "macroBang") => MACRO,
+        (PROC_MACRO, "procMacro") => MACRO,
         (PARENTHESIS, "parenthesis"),
         (PUNCTUATION, "punctuation"),
         (SELF_KEYWORD, "selfKeyword") => KEYWORD,
@@ -143,6 +144,7 @@ define_semantic_token_modifiers![
         (INTRA_DOC_LINK, "intraDocLink"),
         (LIBRARY, "library"),
         (MACRO_MODIFIER, "macro"),
+        (PROC_MACRO_MODIFIER, "proc_macro"),
         (MUTABLE, "mutable"),
         (PUBLIC, "public"),
         (REFERENCE, "reference"),
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index e2b55f4a5c..e77d0c13bf 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -15,6 +15,7 @@ use ide::{
 };
 use ide_db::rust_doc::format_docs;
 use itertools::Itertools;
+use semver::VersionReq;
 use serde_json::to_value;
 use vfs::AbsPath;
 
@@ -56,6 +57,7 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
         SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
         SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE,
         SymbolKind::Macro
+        | SymbolKind::ProcMacro
         | SymbolKind::BuiltinAttr
         | SymbolKind::Attribute
         | SymbolKind::Derive
@@ -138,6 +140,7 @@ pub(crate) fn completion_item_kind(
             SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
             SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
             SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
+            SymbolKind::ProcMacro => lsp_types::CompletionItemKind::FUNCTION,
             SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
             SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
             SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
@@ -443,17 +446,24 @@ pub(crate) fn inlay_hint(
     file_id: FileId,
     inlay_hint: InlayHint,
 ) -> Cancellable {
-    let is_visual_studio_code = snap.config.is_visual_studio_code();
     let needs_resolve = inlay_hint.needs_resolve;
     let (label, tooltip, mut something_to_resolve) =
         inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?;
-    let text_edits =
-        if !is_visual_studio_code && needs_resolve && fields_to_resolve.resolve_text_edits {
-            something_to_resolve |= inlay_hint.text_edit.is_some();
-            None
-        } else {
-            inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
-        };
+
+    let text_edits = if snap
+        .config
+        .visual_studio_code_version()
+        // https://github.com/microsoft/vscode/issues/193124
+        .map_or(true, |version| VersionReq::parse(">=1.86.0").unwrap().matches(version))
+        && needs_resolve
+        && fields_to_resolve.resolve_text_edits
+    {
+        something_to_resolve |= inlay_hint.text_edit.is_some();
+        None
+    } else {
+        inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
+    };
+
     let data = if needs_resolve && something_to_resolve {
         Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.index() }).unwrap())
     } else {
@@ -667,6 +677,7 @@ fn semantic_token_type_and_modifiers(
             SymbolKind::Trait => semantic_tokens::INTERFACE,
             SymbolKind::TraitAlias => semantic_tokens::INTERFACE,
             SymbolKind::Macro => semantic_tokens::MACRO,
+            SymbolKind::ProcMacro => semantic_tokens::PROC_MACRO,
             SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
             SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
         },
@@ -720,6 +731,7 @@ fn semantic_token_type_and_modifiers(
             HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
             HlMod::Library => semantic_tokens::LIBRARY,
             HlMod::Macro => semantic_tokens::MACRO_MODIFIER,
+            HlMod::ProcMacro => semantic_tokens::PROC_MACRO_MODIFIER,
             HlMod::Mutable => semantic_tokens::MUTABLE,
             HlMod::Public => semantic_tokens::PUBLIC,
             HlMod::Reference => semantic_tokens::REFERENCE,
@@ -1507,13 +1519,28 @@ pub(crate) fn test_item(
         id: test_item.id,
         label: test_item.label,
         kind: match test_item.kind {
-            ide::TestItemKind::Crate => lsp_ext::TestItemKind::Package,
+            ide::TestItemKind::Crate(id) => 'b: {
+                let Some((cargo_ws, target)) = snap.cargo_target_for_crate_root(id) else {
+                    break 'b lsp_ext::TestItemKind::Package;
+                };
+                let target = &cargo_ws[target];
+                match target.kind {
+                    project_model::TargetKind::Bin
+                    | project_model::TargetKind::Lib { .. }
+                    | project_model::TargetKind::Example
+                    | project_model::TargetKind::BuildScript
+                    | project_model::TargetKind::Other => lsp_ext::TestItemKind::Package,
+                    project_model::TargetKind::Test | project_model::TargetKind::Bench => {
+                        lsp_ext::TestItemKind::Test
+                    }
+                }
+            }
             ide::TestItemKind::Module => lsp_ext::TestItemKind::Module,
             ide::TestItemKind::Function => lsp_ext::TestItemKind::Test,
         },
         can_resolve_children: matches!(
             test_item.kind,
-            ide::TestItemKind::Crate | ide::TestItemKind::Module
+            ide::TestItemKind::Crate(_) | ide::TestItemKind::Module
         ),
         parent: test_item.parent,
         text_document: test_item
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index bca6db19dc..ffe56e4143 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -488,20 +488,22 @@ impl GlobalState {
 
     fn update_diagnostics(&mut self) {
         let db = self.analysis_host.raw_database();
-        let subscriptions = self
-            .mem_docs
-            .iter()
-            .map(|path| self.vfs.read().0.file_id(path).unwrap())
-            .filter(|&file_id| {
-                let source_root = db.file_source_root(file_id);
-                // Only publish diagnostics for files in the workspace, not from crates.io deps
-                // or the sysroot.
-                // While theoretically these should never have errors, we have quite a few false
-                // positives particularly in the stdlib, and those diagnostics would stay around
-                // forever if we emitted them here.
-                !db.source_root(source_root).is_library
-            })
-            .collect::>();
+        let subscriptions = {
+            let vfs = &self.vfs.read().0;
+            self.mem_docs
+                .iter()
+                .map(|path| vfs.file_id(path).unwrap())
+                .filter(|&file_id| {
+                    let source_root = db.file_source_root(file_id);
+                    // Only publish diagnostics for files in the workspace, not from crates.io deps
+                    // or the sysroot.
+                    // While theoretically these should never have errors, we have quite a few false
+                    // positives particularly in the stdlib, and those diagnostics would stay around
+                    // forever if we emitted them here.
+                    !db.source_root(source_root).is_library
+                })
+                .collect::>()
+        };
         tracing::trace!("updating notifications for {:?}", subscriptions);
 
         // Diagnostics are triggered by the user typing
@@ -797,6 +799,9 @@ impl GlobalState {
                 self.send_notification::(());
                 self.test_run_session = None;
             }
+            flycheck::CargoTestMessage::Custom { text } => {
+                self.send_notification::(text);
+            }
         }
     }
 
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index dfd25abc70..1d831b8b10 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -171,7 +171,7 @@ impl Project<'_> {
                 ..Default::default()
             },
             roots,
-            false,
+            None,
         );
         config.update(self.config).expect("invalid config");
         config.rediscover_workspaces();
diff --git a/crates/span/src/hygiene.rs b/crates/span/src/hygiene.rs
index 4f6d792201..e4b0a26a6f 100644
--- a/crates/span/src/hygiene.rs
+++ b/crates/span/src/hygiene.rs
@@ -26,9 +26,19 @@ use salsa::{InternId, InternValue};
 use crate::MacroCallId;
 
 /// Interned [`SyntaxContextData`].
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct SyntaxContextId(InternId);
 
+impl fmt::Debug for SyntaxContextId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if f.alternate() {
+            write!(f, "{}", self.0.as_u32())
+        } else {
+            f.debug_tuple("SyntaxContextId").field(&self.0).finish()
+        }
+    }
+}
+
 impl salsa::InternKey for SyntaxContextId {
     fn from_intern_id(v: salsa::InternId) -> Self {
         SyntaxContextId(v)
diff --git a/crates/span/src/lib.rs b/crates/span/src/lib.rs
index 0fe3275863..6b849ce373 100644
--- a/crates/span/src/lib.rs
+++ b/crates/span/src/lib.rs
@@ -44,7 +44,10 @@ pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId =
 
 pub type Span = SpanData;
 
-#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+/// Spans represent a region of code, used by the IDE to be able link macro inputs and outputs
+/// together. Positions in spans are relative to some [`SpanAnchor`] to make them more incremental
+/// friendly.
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
 pub struct SpanData {
     /// The text range of this span, relative to the anchor.
     /// We need the anchor for incrementality, as storing absolute ranges will require
@@ -56,9 +59,35 @@ pub struct SpanData {
     pub ctx: Ctx,
 }
 
+impl fmt::Debug for SpanData {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if f.alternate() {
+            fmt::Debug::fmt(&self.anchor.file_id.index(), f)?;
+            f.write_char(':')?;
+            fmt::Debug::fmt(&self.anchor.ast_id.into_raw(), f)?;
+            f.write_char('@')?;
+            fmt::Debug::fmt(&self.range, f)?;
+            f.write_char('#')?;
+            self.ctx.fmt(f)
+        } else {
+            f.debug_struct("SpanData")
+                .field("range", &self.range)
+                .field("anchor", &self.anchor)
+                .field("ctx", &self.ctx)
+                .finish()
+        }
+    }
+}
+
+impl SpanData {
+    pub fn eq_ignoring_ctx(self, other: Self) -> bool {
+        self.anchor == other.anchor && self.range == other.range
+    }
+}
+
 impl Span {
     #[deprecated = "dummy spans will panic if surfaced incorrectly, as such they should be replaced appropriately"]
-    pub const DUMMY: Self = SpanData {
+    pub const DUMMY: Self = Self {
         range: TextRange::empty(TextSize::new(0)),
         anchor: SpanAnchor { file_id: FileId::BOGUS, ast_id: ROOT_ERASED_FILE_AST_ID },
         ctx: SyntaxContextId::ROOT,
diff --git a/crates/span/src/map.rs b/crates/span/src/map.rs
index 9f8101c816..1f396a1e97 100644
--- a/crates/span/src/map.rs
+++ b/crates/span/src/map.rs
@@ -1,23 +1,26 @@
 //! A map that maps a span to every position in a file. Usually maps a span to some range of positions.
 //! Allows bidirectional lookup.
 
-use std::hash::Hash;
+use std::{fmt, hash::Hash};
 
 use stdx::{always, itertools::Itertools};
 use syntax::{TextRange, TextSize};
 use vfs::FileId;
 
-use crate::{ErasedFileAstId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID};
+use crate::{
+    ErasedFileAstId, Span, SpanAnchor, SpanData, SyntaxContextId, ROOT_ERASED_FILE_AST_ID,
+};
 
 /// Maps absolute text ranges for the corresponding file to the relevant span data.
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub struct SpanMap {
-    spans: Vec<(TextSize, S)>,
-    // FIXME: Should be
-    // spans: Vec<(TextSize, crate::SyntaxContextId)>,
+    spans: Vec<(TextSize, SpanData)>,
 }
 
-impl SpanMap {
+impl SpanMap
+where
+    SpanData: Copy,
+{
     /// Creates a new empty [`SpanMap`].
     pub fn empty() -> Self {
         Self { spans: Vec::new() }
@@ -34,7 +37,7 @@ impl SpanMap {
     }
 
     /// Pushes a new span onto the [`SpanMap`].
-    pub fn push(&mut self, offset: TextSize, span: S) {
+    pub fn push(&mut self, offset: TextSize, span: SpanData) {
         if cfg!(debug_assertions) {
             if let Some(&(last_offset, _)) = self.spans.last() {
                 assert!(
@@ -49,13 +52,31 @@ impl SpanMap {
     /// Returns all [`TextRange`]s that correspond to the given span.
     ///
     /// Note this does a linear search through the entire backing vector.
-    pub fn ranges_with_span(&self, span: S) -> impl Iterator + '_
+    pub fn ranges_with_span_exact(&self, span: SpanData) -> impl Iterator + '_
     where
-        S: Eq,
+        S: Copy,
     {
-        // FIXME: This should ignore the syntax context!
         self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| {
-            if s != span {
+            if !s.eq_ignoring_ctx(span) {
+                return None;
+            }
+            let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0);
+            Some(TextRange::new(start, end))
+        })
+    }
+
+    /// Returns all [`TextRange`]s whose spans contain the given span.
+    ///
+    /// Note this does a linear search through the entire backing vector.
+    pub fn ranges_with_span(&self, span: SpanData) -> impl Iterator + '_
+    where
+        S: Copy,
+    {
+        self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| {
+            if s.anchor != span.anchor {
+                return None;
+            }
+            if !s.range.contains_range(span.range) {
                 return None;
             }
             let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0);
@@ -64,21 +85,21 @@ impl SpanMap {
     }
 
     /// Returns the span at the given position.
-    pub fn span_at(&self, offset: TextSize) -> S {
+    pub fn span_at(&self, offset: TextSize) -> SpanData {
         let entry = self.spans.partition_point(|&(it, _)| it <= offset);
         self.spans[entry].1
     }
 
     /// Returns the spans associated with the given range.
     /// In other words, this will return all spans that correspond to all offsets within the given range.
-    pub fn spans_for_range(&self, range: TextRange) -> impl Iterator + '_ {
+    pub fn spans_for_range(&self, range: TextRange) -> impl Iterator> + '_ {
         let (start, end) = (range.start(), range.end());
         let start_entry = self.spans.partition_point(|&(it, _)| it <= start);
         let end_entry = self.spans[start_entry..].partition_point(|&(it, _)| it <= end); // FIXME: this might be wrong?
         self.spans[start_entry..][..end_entry].iter().map(|&(_, s)| s)
     }
 
-    pub fn iter(&self) -> impl Iterator + '_ {
+    pub fn iter(&self) -> impl Iterator)> + '_ {
         self.spans.iter().copied()
     }
 }
@@ -92,6 +113,16 @@ pub struct RealSpanMap {
     end: TextSize,
 }
 
+impl fmt::Display for RealSpanMap {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        writeln!(f, "RealSpanMap({:?}):", self.file_id)?;
+        for span in self.pairs.iter() {
+            writeln!(f, "{}: {}", u32::from(span.0), span.1.into_raw().into_u32())?;
+        }
+        Ok(())
+    }
+}
+
 impl RealSpanMap {
     /// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id).
     pub fn absolute(file_id: FileId) -> Self {
diff --git a/crates/stdx/src/anymap.rs b/crates/stdx/src/anymap.rs
index 899cd8ac6b..d47b3d1647 100644
--- a/crates/stdx/src/anymap.rs
+++ b/crates/stdx/src/anymap.rs
@@ -194,21 +194,6 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox> VacantEntry<'a, A, V> {
 mod tests {
     use super::*;
 
-    #[derive(Clone, Debug, PartialEq)]
-    struct A(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct B(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct C(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct D(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct E(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct F(i32);
-    #[derive(Clone, Debug, PartialEq)]
-    struct J(i32);
-
     #[test]
     fn test_varieties() {
         fn assert_send() {}
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 1bc1ef8434..c3d6f50e6b 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -139,6 +139,17 @@ impl From for ast::Item {
     }
 }
 
+impl From for ast::Item {
+    fn from(extern_item: ast::ExternItem) -> Self {
+        match extern_item {
+            ast::ExternItem::Static(it) => ast::Item::Static(it),
+            ast::ExternItem::Fn(it) => ast::Item::Fn(it),
+            ast::ExternItem::MacroCall(it) => ast::Item::MacroCall(it),
+            ast::ExternItem::TypeAlias(it) => ast::Item::TypeAlias(it),
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum AttrKind {
     Inner,
diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs
index a654366c62..8cf65d11c6 100644
--- a/crates/test-fixture/src/lib.rs
+++ b/crates/test-fixture/src/lib.rs
@@ -149,12 +149,12 @@ impl ChangeFixture {
         for entry in fixture {
             let text = if entry.text.contains(CURSOR_MARKER) {
                 if entry.text.contains(ESCAPED_CURSOR_MARKER) {
-                    entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER).into()
+                    entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
                 } else {
                     let (range_or_offset, text) = extract_range_or_offset(&entry.text);
                     assert!(file_position.is_none());
                     file_position = Some((file_id, range_or_offset));
-                    text.into()
+                    text
                 }
             } else {
                 entry.text.as_str().into()
@@ -251,7 +251,7 @@ impl ChangeFixture {
             fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_owned()));
             roots.push(SourceRoot::new_library(fs));
 
-            source_change.change_file(core_file, Some(mini_core.source_code().into()));
+            source_change.change_file(core_file, Some(mini_core.source_code()));
 
             let all_crates = crate_graph.crates_in_topological_order();
 
@@ -287,7 +287,7 @@ impl ChangeFixture {
             );
             roots.push(SourceRoot::new_library(fs));
 
-            source_change.change_file(proc_lib_file, Some(source.into()));
+            source_change.change_file(proc_lib_file, Some(source));
 
             let all_crates = crate_graph.crates_in_topological_order();
 
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index eec88f8068..28289a6431 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -177,17 +177,19 @@ fn print_debug_subtree(
     let align = "  ".repeat(level);
 
     let Delimiter { kind, open, close } = &subtree.delimiter;
-    let aux = match kind {
-        DelimiterKind::Invisible => format!("$$ {:?} {:?}", open, close),
-        DelimiterKind::Parenthesis => format!("() {:?} {:?}", open, close),
-        DelimiterKind::Brace => format!("{{}} {:?} {:?}", open, close),
-        DelimiterKind::Bracket => format!("[] {:?} {:?}", open, close),
+    let delim = match kind {
+        DelimiterKind::Invisible => "$$",
+        DelimiterKind::Parenthesis => "()",
+        DelimiterKind::Brace => "{}",
+        DelimiterKind::Bracket => "[]",
     };
 
-    if subtree.token_trees.is_empty() {
-        write!(f, "{align}SUBTREE {aux}")?;
-    } else {
-        writeln!(f, "{align}SUBTREE {aux}")?;
+    write!(f, "{align}SUBTREE {delim} ",)?;
+    fmt::Debug::fmt(&open, f)?;
+    write!(f, " ")?;
+    fmt::Debug::fmt(&close, f)?;
+    if !subtree.token_trees.is_empty() {
+        writeln!(f)?;
         for (idx, child) in subtree.token_trees.iter().enumerate() {
             print_debug_token(f, child, level + 1)?;
             if idx != subtree.token_trees.len() - 1 {
@@ -208,16 +210,24 @@ fn print_debug_token(
 
     match tkn {
         TokenTree::Leaf(leaf) => match leaf {
-            Leaf::Literal(lit) => write!(f, "{}LITERAL {} {:?}", align, lit.text, lit.span)?,
-            Leaf::Punct(punct) => write!(
-                f,
-                "{}PUNCH   {} [{}] {:?}",
-                align,
-                punct.char,
-                if punct.spacing == Spacing::Alone { "alone" } else { "joint" },
-                punct.span
-            )?,
-            Leaf::Ident(ident) => write!(f, "{}IDENT   {} {:?}", align, ident.text, ident.span)?,
+            Leaf::Literal(lit) => {
+                write!(f, "{}LITERAL {}", align, lit.text)?;
+                fmt::Debug::fmt(&lit.span, f)?;
+            }
+            Leaf::Punct(punct) => {
+                write!(
+                    f,
+                    "{}PUNCH   {} [{}] ",
+                    align,
+                    punct.char,
+                    if punct.spacing == Spacing::Alone { "alone" } else { "joint" },
+                )?;
+                fmt::Debug::fmt(&punct.span, f)?;
+            }
+            Leaf::Ident(ident) => {
+                write!(f, "{}IDENT   {} ", align, ident.text)?;
+                fmt::Debug::fmt(&ident.span, f)?;
+            }
         },
         TokenTree::Subtree(subtree) => {
             print_debug_subtree(f, subtree, level)?;
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index af5b4e51ef..cf9ad5fe04 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@