Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 34 additions & 8 deletions crates/load-cargo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,21 +554,31 @@ 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()))
.map(ToOwned::to_owned);

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);
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 15 additions & 3 deletions crates/proc-macro-api/src/bidirectional_protocol/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
14 changes: 14 additions & 0 deletions crates/proc-macro-srv-cli/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down
1 change: 1 addition & 0 deletions crates/proc-macro-srv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/proc-macro-srv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 4 additions & 6 deletions crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
15 changes: 15 additions & 0 deletions crates/proc-macro-srv/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
"#]],
);
}
66 changes: 65 additions & 1 deletion crates/proc-macro-srv/src/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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:?}"));
}