Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21405 from tilladam/master
Implement Span::line() and Span::column() for proc-macro server
Lukas Wirth 4 months ago
parent 6a1246b · parent d1a55e6 · commit 86e8b1b
-rw-r--r--Cargo.lock1
-rw-r--r--crates/load-cargo/src/lib.rs42
-rw-r--r--crates/proc-macro-api/src/bidirectional_protocol/msg.rs18
-rw-r--r--crates/proc-macro-srv-cli/src/main_loop.rs14
-rw-r--r--crates/proc-macro-srv/Cargo.toml1
-rw-r--r--crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs10
-rw-r--r--crates/proc-macro-srv/src/lib.rs2
-rw-r--r--crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs10
-rw-r--r--crates/proc-macro-srv/src/tests/mod.rs15
-rw-r--r--crates/proc-macro-srv/src/tests/utils.rs66
10 files changed, 161 insertions, 18 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 42eaeb01f1..8188fbf960 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1864,6 +1864,7 @@ dependencies = [
"intern",
"libc",
"libloading",
+ "line-index 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"memmap2",
"object",
"paths",
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index e8d98b1ce6..33468a5003 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -554,14 +554,12 @@ impl ProcMacroExpander for Expander {
Ok(SubResponse::LocalFilePathResult { name })
}
SubRequest::SourceText { file_id, ast_id, start, end } => {
- let ast_id = span::ErasedFileAstId::from_raw(ast_id);
- let editioned_file_id = span::EditionedFileId::from_raw(file_id);
- let span = Span {
- range: TextRange::new(TextSize::from(start), TextSize::from(end)),
- anchor: SpanAnchor { file_id: editioned_file_id, ast_id },
- ctx: SyntaxContext::root(editioned_file_id.edition()),
- };
- let range = db.resolve_span(span);
+ let range = resolve_sub_span(
+ db,
+ file_id,
+ ast_id,
+ TextRange::new(TextSize::from(start), TextSize::from(end)),
+ );
let source = db.file_text(range.file_id.file_id(db)).text(db);
let text = source
.get(usize::from(range.range.start())..usize::from(range.range.end()))
@@ -569,6 +567,18 @@ impl ProcMacroExpander for Expander {
Ok(SubResponse::SourceTextResult { text })
}
+ SubRequest::LineColumn { file_id, ast_id, offset } => {
+ let range =
+ resolve_sub_span(db, file_id, ast_id, TextRange::empty(TextSize::from(offset)));
+ let source = db.file_text(range.file_id.file_id(db)).text(db);
+ let line_index = ide_db::line_index::LineIndex::new(source);
+ let (line, column) = line_index
+ .try_line_col(range.range.start())
+ .map(|lc| (lc.line + 1, lc.col + 1))
+ .unwrap_or((1, 1));
+ // proc_macro::Span line/column are 1-based
+ Ok(SubResponse::LineColumnResult { line, column })
+ }
SubRequest::FilePath { file_id } => {
let file_id = FileId::from_raw(file_id);
let source_root_id = db.file_source_root(file_id).source_root_id(db);
@@ -603,6 +613,22 @@ impl ProcMacroExpander for Expander {
}
}
+fn resolve_sub_span(
+ db: &dyn ExpandDatabase,
+ file_id: u32,
+ ast_id: u32,
+ range: TextRange,
+) -> hir_expand::FileRange {
+ let ast_id = span::ErasedFileAstId::from_raw(ast_id);
+ let editioned_file_id = span::EditionedFileId::from_raw(file_id);
+ let span = Span {
+ range,
+ anchor: SpanAnchor { file_id: editioned_file_id, ast_id },
+ ctx: SyntaxContext::root(editioned_file_id.edition()),
+ };
+ db.resolve_span(span)
+}
+
#[cfg(test)]
mod tests {
use ide_db::base_db::RootQueryDb;
diff --git a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
index e41f8a5d7d..0e3b700dcc 100644
--- a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
+++ b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
@@ -13,13 +13,25 @@ pub enum SubRequest {
FilePath { file_id: u32 },
SourceText { file_id: u32, ast_id: u32, start: u32, end: u32 },
LocalFilePath { file_id: u32 },
+ LineColumn { file_id: u32, ast_id: u32, offset: u32 },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SubResponse {
- FilePathResult { name: String },
- SourceTextResult { text: Option<String> },
- LocalFilePathResult { name: Option<String> },
+ FilePathResult {
+ name: String,
+ },
+ SourceTextResult {
+ text: Option<String>,
+ },
+ LocalFilePathResult {
+ name: Option<String>,
+ },
+ /// Line and column are 1-based.
+ LineColumnResult {
+ line: u32,
+ column: u32,
+ },
}
#[derive(Debug, Serialize, Deserialize)]
diff --git a/crates/proc-macro-srv-cli/src/main_loop.rs b/crates/proc-macro-srv-cli/src/main_loop.rs
index b2f4b96bd2..22536a4e52 100644
--- a/crates/proc-macro-srv-cli/src/main_loop.rs
+++ b/crates/proc-macro-srv-cli/src/main_loop.rs
@@ -220,6 +220,20 @@ impl<C: Codec> proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandl
_ => None,
}
}
+
+ fn line_column(&mut self, span: proc_macro_srv::span::Span) -> Option<(u32, u32)> {
+ let proc_macro_srv::span::Span { range, anchor, ctx: _ } = span;
+ match self.roundtrip(bidirectional::SubRequest::LineColumn {
+ file_id: anchor.file_id.as_u32(),
+ ast_id: anchor.ast_id.into_raw(),
+ offset: range.start().into(),
+ }) {
+ Some(bidirectional::BidirectionalMessage::SubResponse(
+ bidirectional::SubResponse::LineColumnResult { line, column },
+ )) => Some((line, column)),
+ _ => None,
+ }
+ }
}
fn handle_expand_ra<C: Codec>(
diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml
index 3610171784..8e5617f8a2 100644
--- a/crates/proc-macro-srv/Cargo.toml
+++ b/crates/proc-macro-srv/Cargo.toml
@@ -31,6 +31,7 @@ libc.workspace = true
[dev-dependencies]
expect-test.workspace = true
+line-index.workspace = true
# used as proc macro test targets
proc-macro-test.path = "./proc-macro-test"
diff --git a/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs b/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs
index b4fac26d6e..06c76b6d03 100644
--- a/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs
+++ b/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs
@@ -79,6 +79,16 @@ pub fn fn_like_span_ops(args: TokenStream) -> TokenStream {
TokenStream::from_iter(vec![first, second, third])
}
+/// Returns the line and column of the first token's span as two integer literals.
+#[proc_macro]
+pub fn fn_like_span_line_column(args: TokenStream) -> TokenStream {
+ let first = args.into_iter().next().unwrap();
+ let span = first.span();
+ let line = Literal::usize_unsuffixed(span.line());
+ let column = Literal::usize_unsuffixed(span.column());
+ TokenStream::from_iter(vec![TokenTree::Literal(line), TokenTree::Literal(column)])
+}
+
#[proc_macro_attribute]
pub fn attr_noop(_args: TokenStream, item: TokenStream) -> TokenStream {
item
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index f2d1dfbba4..c1ef49a717 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -98,6 +98,8 @@ pub trait ProcMacroClientInterface {
fn file(&mut self, file_id: span::FileId) -> String;
fn source_text(&mut self, span: Span) -> Option<String>;
fn local_file(&mut self, file_id: span::FileId) -> Option<String>;
+ /// Line and column are 1-based.
+ fn line_column(&mut self, span: Span) -> Option<(u32, u32)>;
}
const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
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
index 32725afc55..3a25391b57 100644
--- a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
+++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -257,14 +257,12 @@ impl server::Span for RaSpanServer<'_> {
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
- 1
+ fn 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 column(&mut self, _span: Self::Span) -> usize {
- // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL
- 1
+ fn 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)
}
}
diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs
index 20507a6def..ebef9a9a51 100644
--- a/crates/proc-macro-srv/src/tests/mod.rs
+++ b/crates/proc-macro-srv/src/tests/mod.rs
@@ -703,6 +703,7 @@ fn list_test_macros() {
fn_like_mk_idents [Bang]
fn_like_span_join [Bang]
fn_like_span_ops [Bang]
+ fn_like_span_line_column [Bang]
attr_noop [Attr]
attr_panic [Attr]
attr_error [Attr]
@@ -712,3 +713,17 @@ fn list_test_macros() {
DeriveError [CustomDerive]"#]]
.assert_eq(&res);
}
+
+#[test]
+fn test_fn_like_span_line_column() {
+ assert_expand_with_callback(
+ "fn_like_span_line_column",
+ // Input text with known position: "hello" starts at offset 1 (line 2, column 1 in 1-based)
+ "
+hello",
+ expect![[r#"
+ LITER 42:Root[0000, 0]@0..100#ROOT2024 Integer 2
+ LITER 42:Root[0000, 0]@0..100#ROOT2024 Integer 1
+ "#]],
+ );
+}
diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs
index 61fcd810b1..81ff1965d6 100644
--- a/crates/proc-macro-srv/src/tests/utils.rs
+++ b/crates/proc-macro-srv/src/tests/utils.rs
@@ -6,7 +6,8 @@ use span::{
};
use crate::{
- EnvSnapshot, ProcMacroSrv, SpanId, dylib, proc_macro_test_dylib_path, token_stream::TokenStream,
+ EnvSnapshot, ProcMacroClientInterface, ProcMacroSrv, SpanId, dylib, proc_macro_test_dylib_path,
+ token_stream::TokenStream,
};
fn parse_string(call_site: SpanId, src: &str) -> TokenStream<SpanId> {
@@ -109,3 +110,66 @@ pub(crate) fn list() -> Vec<String> {
let res = srv.list_macros(&dylib_path).unwrap();
res.into_iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect()
}
+
+/// A mock callback for testing that computes line/column from the input text.
+struct MockCallback<'a> {
+ text: &'a str,
+}
+
+impl ProcMacroClientInterface for MockCallback<'_> {
+ fn source_text(&mut self, span: Span) -> Option<String> {
+ self.text
+ .get(usize::from(span.range.start())..usize::from(span.range.end()))
+ .map(ToOwned::to_owned)
+ }
+
+ fn file(&mut self, _file_id: FileId) -> String {
+ String::new()
+ }
+
+ fn local_file(&mut self, _file_id: FileId) -> Option<String> {
+ None
+ }
+
+ fn line_column(&mut self, span: Span) -> Option<(u32, u32)> {
+ let line_index = line_index::LineIndex::new(self.text);
+ let line_col = line_index.try_line_col(span.range.start())?;
+ // proc_macro uses 1-based line/column
+ Some((line_col.line as u32 + 1, line_col.col as u32 + 1))
+ }
+}
+
+pub fn assert_expand_with_callback(
+ macro_name: &str,
+ #[rust_analyzer::rust_fixture] ra_fixture: &str,
+ expect_spanned: Expect,
+) {
+ let path = proc_macro_test_dylib_path();
+ let expander = dylib::Expander::new(&temp_dir::TempDir::new().unwrap(), &path).unwrap();
+
+ let def_site = Span {
+ range: TextRange::new(0.into(), 150.into()),
+ anchor: SpanAnchor {
+ file_id: EditionedFileId::current_edition(FileId::from_raw(41)),
+ ast_id: ROOT_ERASED_FILE_AST_ID,
+ },
+ ctx: SyntaxContext::root(span::Edition::CURRENT),
+ };
+ let call_site = Span {
+ range: TextRange::new(0.into(), 100.into()),
+ anchor: SpanAnchor {
+ file_id: EditionedFileId::current_edition(FileId::from_raw(42)),
+ ast_id: ROOT_ERASED_FILE_AST_ID,
+ },
+ ctx: SyntaxContext::root(span::Edition::CURRENT),
+ };
+ let mixed_site = call_site;
+
+ let fixture = parse_string_spanned(call_site.anchor, call_site.ctx, ra_fixture);
+
+ let mut callback = MockCallback { text: ra_fixture };
+ let res = expander
+ .expand(macro_name, fixture, None, def_site, call_site, mixed_site, Some(&mut callback))
+ .unwrap();
+ expect_spanned.assert_eq(&format!("{res:?}"));
+}