Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs')
-rw-r--r--crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs476
1 files changed, 476 insertions, 0 deletions
diff --git a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
new file mode 100644
index 0000000000..bb174ba1b2
--- /dev/null
+++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -0,0 +1,476 @@
+//! 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},
+ iter,
+ ops::{Bound, Range},
+};
+
+use proc_macro::bridge::{self, server};
+use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
+use tt::{TextRange, TextSize};
+
+use crate::server_impl::{
+ delim_to_external, delim_to_internal, literal_with_stringify_parts,
+ token_stream::TokenStreamBuilder, Symbol, SymbolInternerRef, SYMBOL_INTERNER,
+};
+mod tt {
+ pub use tt::*;
+
+ pub type Subtree = ::tt::Subtree<super::Span>;
+ pub type TokenTree = ::tt::TokenTree<super::Span>;
+ pub type Leaf = ::tt::Leaf<super::Span>;
+ pub type Literal = ::tt::Literal<super::Span>;
+ pub type Punct = ::tt::Punct<super::Span>;
+ pub type Ident = ::tt::Ident<super::Span>;
+}
+
+type TokenStream = crate::server_impl::TokenStream<Span>;
+
+#[derive(Clone)]
+pub struct SourceFile;
+pub struct FreeFunctions;
+
+pub struct RaSpanServer {
+ pub(crate) interner: SymbolInternerRef,
+ // FIXME: Report this back to the caller to track as dependencies
+ pub tracked_env_vars: HashMap<Box<str>, Option<Box<str>>>,
+ // FIXME: Report this back to the caller to track as dependencies
+ pub tracked_paths: HashSet<Box<str>>,
+ pub call_site: Span,
+ pub def_site: Span,
+ pub mixed_site: Span,
+}
+
+impl server::Types for RaSpanServer {
+ type FreeFunctions = FreeFunctions;
+ type TokenStream = TokenStream;
+ type SourceFile = SourceFile;
+ type Span = Span;
+ type Symbol = Symbol;
+}
+
+impl server::FreeFunctions for RaSpanServer {
+ fn injected_env_var(&mut self, _: &str) -> Option<std::string::String> {
+ 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<bridge::Literal<Self::Span, Self::Symbol>, ()> {
+ use proc_macro::bridge::LitKind;
+ use rustc_lexer::{LiteralKind, Token, TokenKind};
+
+ let mut tokens = rustc_lexer::tokenize(s);
+ let minus_or_lit = tokens.next().unwrap_or(Token { kind: TokenKind::Eof, len: 0 });
+
+ let lit = if minus_or_lit.kind == TokenKind::Minus {
+ let lit = tokens.next().ok_or(())?;
+ if !matches!(
+ lit.kind,
+ TokenKind::Literal {
+ kind: LiteralKind::Int { .. } | LiteralKind::Float { .. },
+ ..
+ }
+ ) {
+ return Err(());
+ }
+ lit
+ } else {
+ minus_or_lit
+ };
+
+ if tokens.next().is_some() {
+ return Err(());
+ }
+
+ let TokenKind::Literal { kind, suffix_start } = lit.kind else { return Err(()) };
+ let (kind, start_offset, end_offset) = match kind {
+ LiteralKind::Int { .. } => (LitKind::Integer, 0, 0),
+ LiteralKind::Float { .. } => (LitKind::Float, 0, 0),
+ LiteralKind::Char { terminated } => (LitKind::Char, 1, terminated as usize),
+ LiteralKind::Byte { terminated } => (LitKind::Byte, 2, terminated as usize),
+ LiteralKind::Str { terminated } => (LitKind::Str, 1, terminated as usize),
+ LiteralKind::ByteStr { terminated } => (LitKind::ByteStr, 2, terminated as usize),
+ LiteralKind::CStr { terminated } => (LitKind::CStr, 2, terminated as usize),
+ LiteralKind::RawStr { n_hashes } => (
+ LitKind::StrRaw(n_hashes.unwrap_or_default()),
+ 2 + n_hashes.unwrap_or_default() as usize,
+ 1 + n_hashes.unwrap_or_default() as usize,
+ ),
+ LiteralKind::RawByteStr { n_hashes } => (
+ LitKind::ByteStrRaw(n_hashes.unwrap_or_default()),
+ 3 + n_hashes.unwrap_or_default() as usize,
+ 1 + n_hashes.unwrap_or_default() as usize,
+ ),
+ LiteralKind::RawCStr { n_hashes } => (
+ LitKind::CStrRaw(n_hashes.unwrap_or_default()),
+ 3 + n_hashes.unwrap_or_default() as usize,
+ 1 + n_hashes.unwrap_or_default() as usize,
+ ),
+ };
+
+ let (lit, suffix) = s.split_at(suffix_start as usize);
+ let lit = &lit[start_offset..lit.len() - end_offset];
+ let suffix = match suffix {
+ "" | "_" => None,
+ suffix => Some(Symbol::intern(self.interner, suffix)),
+ };
+
+ Ok(bridge::Literal {
+ kind,
+ symbol: Symbol::intern(self.interner, lit),
+ suffix,
+ span: self.call_site,
+ })
+ }
+
+ fn emit_diagnostic(&mut self, _: bridge::Diagnostic<Self::Span>) {
+ // FIXME handle diagnostic
+ }
+}
+
+impl server::TokenStream for RaSpanServer {
+ fn is_empty(&mut self, stream: &Self::TokenStream) -> bool {
+ stream.is_empty()
+ }
+ fn from_str(&mut self, src: &str) -> Self::TokenStream {
+ Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string")
+ }
+ fn to_string(&mut self, stream: &Self::TokenStream) -> String {
+ stream.to_string()
+ }
+ fn from_token_tree(
+ &mut self,
+ tree: bridge::TokenTree<Self::TokenStream, Self::Span, Self::Symbol>,
+ ) -> Self::TokenStream {
+ match tree {
+ bridge::TokenTree::Group(group) => {
+ let group = tt::Subtree {
+ delimiter: delim_to_internal(group.delimiter, group.span),
+ token_trees: match group.stream {
+ Some(stream) => stream.into_iter().collect(),
+ None => Box::new([]),
+ },
+ };
+ let tree = tt::TokenTree::from(group);
+ Self::TokenStream::from_iter(iter::once(tree))
+ }
+
+ bridge::TokenTree::Ident(ident) => {
+ let text = ident.sym.text(self.interner);
+ let text =
+ if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text };
+ let ident: tt::Ident = tt::Ident { text, span: ident.span };
+ let leaf = tt::Leaf::from(ident);
+ let tree = tt::TokenTree::from(leaf);
+ Self::TokenStream::from_iter(iter::once(tree))
+ }
+
+ bridge::TokenTree::Literal(literal) => {
+ let text = literal_with_stringify_parts(&literal, self.interner, |parts| {
+ ::tt::SmolStr::from_iter(parts.iter().copied())
+ });
+
+ let literal = tt::Literal { text, span: literal.span };
+ let leaf: tt::Leaf = tt::Leaf::from(literal);
+ let tree = tt::TokenTree::from(leaf);
+ Self::TokenStream::from_iter(iter::once(tree))
+ }
+
+ bridge::TokenTree::Punct(p) => {
+ let punct = tt::Punct {
+ char: p.ch as char,
+ spacing: if p.joint { tt::Spacing::Joint } else { tt::Spacing::Alone },
+ span: p.span,
+ };
+ let leaf = tt::Leaf::from(punct);
+ let tree = tt::TokenTree::from(leaf);
+ Self::TokenStream::from_iter(iter::once(tree))
+ }
+ }
+ }
+
+ fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result<Self::TokenStream, ()> {
+ // 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 concat_trees(
+ &mut self,
+ base: Option<Self::TokenStream>,
+ trees: Vec<bridge::TokenTree<Self::TokenStream, Self::Span, Self::Symbol>>,
+ ) -> Self::TokenStream {
+ let mut builder = TokenStreamBuilder::new();
+ if let Some(base) = base {
+ builder.push(base);
+ }
+ for tree in trees {
+ builder.push(self.from_token_tree(tree));
+ }
+ builder.build()
+ }
+
+ fn concat_streams(
+ &mut self,
+ base: Option<Self::TokenStream>,
+ streams: Vec<Self::TokenStream>,
+ ) -> Self::TokenStream {
+ let mut builder = TokenStreamBuilder::new();
+ if let Some(base) = base {
+ builder.push(base);
+ }
+ for stream in streams {
+ builder.push(stream);
+ }
+ builder.build()
+ }
+
+ fn into_trees(
+ &mut self,
+ stream: Self::TokenStream,
+ ) -> Vec<bridge::TokenTree<Self::TokenStream, Self::Span, Self::Symbol>> {
+ stream
+ .into_iter()
+ .map(|tree| match tree {
+ tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
+ bridge::TokenTree::Ident(match ident.text.strip_prefix("r#") {
+ Some(text) => bridge::Ident {
+ sym: Symbol::intern(self.interner, text),
+ is_raw: true,
+ span: ident.span,
+ },
+ None => bridge::Ident {
+ sym: Symbol::intern(self.interner, &ident.text),
+ is_raw: false,
+ span: ident.span,
+ },
+ })
+ }
+ tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
+ bridge::TokenTree::Literal(bridge::Literal {
+ span: lit.span,
+ ..server::FreeFunctions::literal_from_str(self, &lit.text).unwrap()
+ })
+ }
+ tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => {
+ bridge::TokenTree::Punct(bridge::Punct {
+ ch: punct.char as u8,
+ joint: punct.spacing == tt::Spacing::Joint,
+ span: punct.span,
+ })
+ }
+ tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(bridge::Group {
+ delimiter: delim_to_external(subtree.delimiter),
+ stream: if subtree.token_trees.is_empty() {
+ None
+ } else {
+ Some(subtree.token_trees.into_vec().into_iter().collect())
+ },
+ span: bridge::DelimSpan::from_single(subtree.delimiter.open),
+ }),
+ })
+ .collect()
+ }
+}
+
+impl server::SourceFile for RaSpanServer {
+ fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool {
+ // FIXME
+ true
+ }
+ fn path(&mut self, _file: &Self::SourceFile) -> String {
+ // FIXME
+ String::new()
+ }
+ fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
+ true
+ }
+}
+
+impl server::Span for RaSpanServer {
+ fn debug(&mut self, span: Self::Span) -> String {
+ format!("{:?}", span)
+ }
+ fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile {
+ // FIXME stub, requires db
+ SourceFile {}
+ }
+ fn 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 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 source_text(&mut self, _span: Self::Span) -> Option<String> {
+ // FIXME requires db, needs special handling wrt fixup spans
+ None
+ }
+
+ fn parent(&mut self, _span: Self::Span) -> Option<Self::Span> {
+ // FIXME requires db, looks up the parent call site
+ None
+ }
+ fn source(&mut self, span: Self::Span) -> Self::Span {
+ // FIXME requires db, returns the top level call site
+ span
+ }
+ fn byte_range(&mut self, span: Self::Span) -> Range<usize> {
+ // FIXME requires db to resolve the ast id, THIS IS NOT INCREMENTAL
+ Range { start: span.range.start().into(), end: span.range.end().into() }
+ }
+ fn join(&mut self, first: Self::Span, second: Self::Span) -> Option<Self::Span> {
+ // 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 subspan(
+ &mut self,
+ span: Self::Span,
+ start: Bound<usize>,
+ end: Bound<usize>,
+ ) -> Option<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 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 resolved_at(&mut self, span: Self::Span, at: Self::Span) -> Self::Span {
+ Span { ctx: at.ctx, ..span }
+ }
+
+ fn 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 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 line(&mut self, _span: Self::Span) -> usize {
+ // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL
+ 0
+ }
+
+ fn column(&mut self, _span: Self::Span) -> usize {
+ // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL
+ 0
+ }
+}
+
+impl server::Symbol for RaSpanServer {
+ fn normalize_and_validate_ident(&mut self, string: &str) -> Result<Self::Symbol, ()> {
+ // FIXME: nfc-normalize and validate idents
+ Ok(<Self as server::Server>::intern_symbol(string))
+ }
+}
+
+impl server::Server for RaSpanServer {
+ fn globals(&mut self) -> bridge::ExpnGlobals<Self::Span> {
+ bridge::ExpnGlobals {
+ def_site: self.def_site,
+ call_site: self.call_site,
+ mixed_site: self.mixed_site,
+ }
+ }
+
+ fn intern_symbol(ident: &str) -> Self::Symbol {
+ // FIXME: should be `self.interner` once the proc-macro api allows it.
+ Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident))
+ }
+
+ fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) {
+ // FIXME: should be `self.interner` once the proc-macro api allows it.
+ f(symbol.text(&SYMBOL_INTERNER).as_str())
+ }
+}