Merge pull request #21405 from tilladam/master
Implement Span::line() and Span::column() for proc-macro server
This commit is contained in:
commit
fd1457d742
10 changed files with 161 additions and 18 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue