//! proc-macro server backend based on rust-analyzer's internal span representation //! This backend is used solely by rust-analyzer as it ties into rust-analyzer internals. //! //! It is an unfortunate result of how the proc-macro API works that we need to look into the //! concrete representation of the spans, and as such, RustRover cannot make use of this unless they //! change their representation to be compatible with rust-analyzer's. use std::{ collections::{HashMap, HashSet}, ops::{Bound, Range}, }; use intern::Symbol; use rustc_proc_macro::bridge::server; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span, TextRange, TextSize}; use crate::{ ProcMacroClientHandle, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; pub struct RaSpanServer<'a> { // FIXME: Report this back to the caller to track as dependencies pub tracked_env_vars: HashMap, Option>>, // FIXME: Report this back to the caller to track as dependencies pub tracked_paths: HashSet>, pub call_site: Span, pub def_site: Span, pub mixed_site: Span, pub callback: Option>, } impl server::Server for RaSpanServer<'_> { type TokenStream = crate::token_stream::TokenStream; type Span = Span; type Symbol = Symbol; fn globals(&mut self) -> ExpnGlobals { ExpnGlobals { def_site: self.def_site, call_site: self.call_site, mixed_site: self.mixed_site, } } fn intern_symbol(ident: &str) -> Self::Symbol { Symbol::intern(ident) } fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { f(symbol.as_str()) } fn injected_env_var(&mut self, _: &str) -> Option { None } fn track_env_var(&mut self, var: &str, value: Option<&str>) { self.tracked_env_vars.insert(var.into(), value.map(Into::into)); } fn track_path(&mut self, path: &str) { self.tracked_paths.insert(path.into()); } fn literal_from_str(&mut self, s: &str) -> Result, String> { literal_from_str(s, self.call_site) .map_err(|()| "cannot parse string into literal".to_string()) } fn emit_diagnostic(&mut self, _: Diagnostic) { // FIXME handle diagnostic } fn ts_drop(&mut self, stream: Self::TokenStream) { drop(stream); } fn ts_clone(&mut self, stream: &Self::TokenStream) -> Self::TokenStream { stream.clone() } fn ts_is_empty(&mut self, stream: &Self::TokenStream) -> bool { stream.is_empty() } fn ts_from_str(&mut self, src: &str) -> Result { Self::TokenStream::from_str(src, self.call_site) .map_err(|e| format!("failed to parse str to token stream: {e}")) } fn ts_to_string(&mut self, stream: &Self::TokenStream) -> String { stream.to_string() } fn ts_from_token_tree(&mut self, tree: TokenTree) -> Self::TokenStream { Self::TokenStream::new(vec![tree]) } fn ts_expand_expr(&mut self, self_: &Self::TokenStream) -> Result { // FIXME: requires db, more importantly this requires name resolution so we would need to // eagerly expand this proc-macro, but we can't know that this proc-macro is eager until we // expand it ... // This calls for some kind of marker that a proc-macro wants to access this eager API, // otherwise we need to treat every proc-macro eagerly / or not support this. Ok(self_.clone()) } fn ts_concat_trees( &mut self, base: Option, trees: Vec>, ) -> Self::TokenStream { match base { Some(mut base) => { for tt in trees { base.push_tree(tt); } base } None => Self::TokenStream::new(trees), } } fn ts_concat_streams( &mut self, base: Option, streams: Vec, ) -> Self::TokenStream { let mut stream = base.unwrap_or_default(); for s in streams { stream.push_stream(s); } stream } fn ts_into_trees(&mut self, stream: Self::TokenStream) -> Vec> { (*stream.0).clone() } fn span_debug(&mut self, span: Self::Span) -> String { format!("{:?}", span) } fn span_file(&mut self, span: Self::Span) -> String { self.callback.as_mut().map(|cb| cb.file(span.anchor.file_id.file_id())).unwrap_or_default() } fn span_local_file(&mut self, span: Self::Span) -> Option { self.callback.as_mut().and_then(|cb| cb.local_file(span.anchor.file_id.file_id())) } fn span_save_span(&mut self, _span: Self::Span) -> usize { // FIXME, quote is incompatible with third-party tools // This is called by the quote proc-macro which is expanded when the proc-macro is compiled // As such, r-a will never observe this 0 } fn span_recover_proc_macro_span(&mut self, _id: usize) -> Self::Span { // FIXME, quote is incompatible with third-party tools // This is called by the expansion of quote!, r-a will observe this, but we don't have // access to the spans that were encoded self.call_site } /// Recent feature, not yet in the proc_macro /// /// See PR: /// https://github.com/rust-lang/rust/pull/55780 fn span_source_text(&mut self, span: Self::Span) -> Option { self.callback.as_mut()?.source_text(span) } fn span_parent(&mut self, _span: Self::Span) -> Option { // FIXME requires db, looks up the parent call site None } fn span_source(&mut self, span: Self::Span) -> Self::Span { if let Some(ref mut callback) = self.callback { return callback.span_source(span); } span } fn span_byte_range(&mut self, span: Self::Span) -> Range { if let Some(cb) = self.callback.as_mut() { return cb.byte_range(span); } Range { start: span.range.start().into(), end: span.range.end().into() } } fn span_join(&mut self, first: Self::Span, second: Self::Span) -> Option { // We can't modify the span range for fixup spans, those are meaningful to fixup, so just // prefer the non-fixup span. if first.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { return Some(second); } if second.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { return Some(first); } // FIXME: Once we can talk back to the client, implement a "long join" request for anchors // that differ in [AstId]s as joining those spans requires resolving the AstIds. if first.anchor != second.anchor { return None; } // Differing context, we can't merge these so prefer the one that's root if first.ctx != second.ctx { if first.ctx.is_root() { return Some(second); } else if second.ctx.is_root() { return Some(first); } } Some(Span { range: first.range.cover(second.range), anchor: second.anchor, ctx: second.ctx, }) } fn span_subspan( &mut self, span: Self::Span, start: Bound, end: Bound, ) -> Option { // We can't modify the span range for fixup spans, those are meaningful to fixup. if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { return Some(span); } let length = span.range.len().into(); let start: u32 = match start { Bound::Included(lo) => lo, Bound::Excluded(lo) => lo.checked_add(1)?, Bound::Unbounded => 0, } .try_into() .ok()?; let end: u32 = match end { Bound::Included(hi) => hi.checked_add(1)?, Bound::Excluded(hi) => hi, Bound::Unbounded => span.range.len().into(), } .try_into() .ok()?; // Bounds check the values, preventing addition overflow and OOB spans. let span_start = span.range.start().into(); if (u32::MAX - start) < span_start || (u32::MAX - end) < span_start || start >= end || end > length { return None; } Some(Span { range: TextRange::new(TextSize::from(start), TextSize::from(end)) + span.range.start(), ..span }) } fn span_resolved_at(&mut self, span: Self::Span, at: Self::Span) -> Self::Span { Span { ctx: at.ctx, ..span } } fn span_end(&mut self, span: Self::Span) -> Self::Span { // We can't modify the span range for fixup spans, those are meaningful to fixup. if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { return span; } Span { range: TextRange::empty(span.range.end()), ..span } } fn span_start(&mut self, span: Self::Span) -> Self::Span { // We can't modify the span range for fixup spans, those are meaningful to fixup. if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { return span; } Span { range: TextRange::empty(span.range.start()), ..span } } fn span_line(&mut self, span: Self::Span) -> usize { self.callback.as_mut().and_then(|cb| cb.line_column(span)).map_or(1, |(l, _)| l as usize) } fn span_column(&mut self, span: Self::Span) -> usize { self.callback.as_mut().and_then(|cb| cb.line_column(span)).map_or(1, |(_, c)| c as usize) } fn symbol_normalize_and_validate_ident(&mut self, string: &str) -> Result { // FIXME: nfc-normalize and validate idents Ok(::intern_symbol(string)) } }