Skip to content

Commit 20e09c6

Browse files
committed
Auto merge of rust-lang#16088 - Veykril:proc-macro-srv-2, r=Veykril
feat: Implement a rust-analyzer span backed proc-macro server mode This implements the basic span APIs. Basically anything that doesn't require talking back to the client for information access. This also commits our syntax fixup marker to use an `ErasedAstFileId` of `!0-1` aka `0xffff_fffe`, instead of using a dummy FileId as a marker, as we need that for the `SourceFile` API to be implementable. The reason as to why the server needs to know about this at all is to prevent it from creating invalid fixup spans which could make r-a panic.
2 parents 3ce3593 + 5761b50 commit 20e09c6

File tree

31 files changed

+1479
-654
lines changed

31 files changed

+1479
-654
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ jobs:
3838
- 'crates/proc-macro-api/**'
3939
- 'crates/proc-macro-srv/**'
4040
- 'crates/proc-macro-srv-cli/**'
41-
- 'crates/proc-macro-test/**'
4241
4342
rust:
4443
needs: changes

Cargo.lock

Lines changed: 2 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
members = ["xtask/", "lib/*", "crates/*"]
3-
exclude = ["crates/proc-macro-test/imp"]
3+
exclude = ["crates/proc-macro-srv/proc-macro-test/"]
44
resolver = "2"
55

66
[workspace.package]
@@ -81,7 +81,6 @@ vfs = { path = "./crates/vfs", version = "0.0.0" }
8181
rustc-dependencies = { path = "./crates/rustc-dependencies", version = "0.0.0" }
8282

8383
# local crates that aren't published to crates.io. These should not have versions.
84-
proc-macro-test = { path = "./crates/proc-macro-test" }
8584
sourcegen = { path = "./crates/sourcegen" }
8685
test-fixture = { path = "./crates/test-fixture" }
8786
test-utils = { path = "./crates/test-utils" }

crates/hir-expand/src/builtin_fn_macro.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ register_builtin! {
108108
(format_args, FormatArgs) => format_args_expand,
109109
(const_format_args, ConstFormatArgs) => format_args_expand,
110110
(format_args_nl, FormatArgsNl) => format_args_nl_expand,
111+
(quote, Quote) => quote_expand,
111112

112113
EAGER:
113114
(compile_error, CompileError) => compile_error_expand,
@@ -770,3 +771,15 @@ fn option_env_expand(
770771

771772
ExpandResult::ok(expanded)
772773
}
774+
775+
fn quote_expand(
776+
_db: &dyn ExpandDatabase,
777+
_arg_id: MacroCallId,
778+
_tt: &tt::Subtree,
779+
span: Span,
780+
) -> ExpandResult<tt::Subtree> {
781+
ExpandResult::new(
782+
tt::Subtree::empty(tt::DelimSpan { open: span, close: span }),
783+
ExpandError::other("quote! is not implemented"),
784+
)
785+
}

crates/hir-expand/src/fixup.rs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
//! To make attribute macros work reliably when typing, we need to take care to
22
//! fix up syntax errors in the code we're passing to them.
33
4-
use la_arena::RawIdx;
54
use rustc_hash::{FxHashMap, FxHashSet};
65
use smallvec::SmallVec;
7-
use span::{ErasedFileAstId, FileId, Span, SpanAnchor, SpanData};
6+
use span::{ErasedFileAstId, Span, SpanAnchor, SpanData, FIXUP_ERASED_FILE_AST_ID_MARKER};
87
use stdx::never;
98
use syntax::{
109
ast::{self, AstNode, HasLoopBody},
@@ -39,13 +38,11 @@ impl SyntaxFixupUndoInfo {
3938
pub(crate) const NONE: Self = SyntaxFixupUndoInfo { original: None };
4039
}
4140

42-
// censoring -> just don't convert the node
43-
// replacement -> censor + append
44-
// append -> insert a fake node, here we need to assemble some dummy span that we can figure out how
45-
// to remove later
46-
const FIXUP_DUMMY_FILE: FileId = FileId::from_raw(FileId::MAX_FILE_ID);
47-
const FIXUP_DUMMY_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(RawIdx::from_u32(!0));
41+
// We mark spans with `FIXUP_DUMMY_AST_ID` to indicate that they are fake.
42+
const FIXUP_DUMMY_AST_ID: ErasedFileAstId = FIXUP_ERASED_FILE_AST_ID_MARKER;
4843
const FIXUP_DUMMY_RANGE: TextRange = TextRange::empty(TextSize::new(0));
44+
// If the fake span has this range end, that means that the range start is an index into the
45+
// `original` list in `SyntaxFixupUndoInfo`.
4946
const FIXUP_DUMMY_RANGE_END: TextSize = TextSize::new(!0);
5047

5148
pub(crate) fn fixup_syntax(
@@ -58,13 +55,13 @@ pub(crate) fn fixup_syntax(
5855
let mut preorder = node.preorder();
5956
let mut original = Vec::new();
6057
let dummy_range = FIXUP_DUMMY_RANGE;
61-
// we use a file id of `FileId(!0)` to signal a fake node, and the text range's start offset as
62-
// the index into the replacement vec but only if the end points to !0
63-
let dummy_anchor = SpanAnchor { file_id: FIXUP_DUMMY_FILE, ast_id: FIXUP_DUMMY_AST_ID };
64-
let fake_span = |range| SpanData {
65-
range: dummy_range,
66-
anchor: dummy_anchor,
67-
ctx: span_map.span_for_range(range).ctx,
58+
let fake_span = |range| {
59+
let span = span_map.span_for_range(range);
60+
SpanData {
61+
range: dummy_range,
62+
anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor },
63+
ctx: span.ctx,
64+
}
6865
};
6966
while let Some(event) = preorder.next() {
7067
let syntax::WalkEvent::Enter(node) = event else { continue };
@@ -76,12 +73,13 @@ pub(crate) fn fixup_syntax(
7673
let original_tree = mbe::syntax_node_to_token_tree(&node, span_map, call_site);
7774
let idx = original.len() as u32;
7875
original.push(original_tree);
76+
let span = span_map.span_for_range(node_range);
7977
let replacement = Leaf::Ident(Ident {
8078
text: "__ra_fixup".into(),
8179
span: SpanData {
8280
range: TextRange::new(TextSize::new(idx), FIXUP_DUMMY_RANGE_END),
83-
anchor: dummy_anchor,
84-
ctx: span_map.span_for_range(node_range).ctx,
81+
anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor },
82+
ctx: span.ctx,
8583
},
8684
});
8785
append.insert(node.clone().into(), vec![replacement]);
@@ -304,8 +302,8 @@ pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo)
304302
let undo_info = &**undo_info;
305303
#[allow(deprecated)]
306304
if never!(
307-
tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
308-
|| tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
305+
tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
306+
|| tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
309307
) {
310308
tt.delimiter.close = SpanData::DUMMY;
311309
tt.delimiter.open = SpanData::DUMMY;
@@ -321,16 +319,16 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
321319
.filter(|tt| match tt {
322320
tt::TokenTree::Leaf(leaf) => {
323321
let span = leaf.span();
324-
let is_real_leaf = span.anchor.file_id != FIXUP_DUMMY_FILE;
322+
let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID;
325323
let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
326324
is_real_leaf || is_replaced_node
327325
}
328326
tt::TokenTree::Subtree(_) => true,
329327
})
330328
.flat_map(|tt| match tt {
331329
tt::TokenTree::Subtree(mut tt) => {
332-
if tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
333-
|| tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
330+
if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
331+
|| tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
334332
{
335333
// Even though fixup never creates subtrees with fixup spans, the old proc-macro server
336334
// might copy them if the proc-macro asks for it, so we need to filter those out
@@ -341,7 +339,7 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
341339
SmallVec::from_const([tt.into()])
342340
}
343341
tt::TokenTree::Leaf(leaf) => {
344-
if leaf.span().anchor.file_id == FIXUP_DUMMY_FILE {
342+
if leaf.span().anchor.ast_id == FIXUP_DUMMY_AST_ID {
345343
// we have a fake node here, we need to replace it again with the original
346344
let original = undo_info[u32::from(leaf.span().range.start()) as usize].clone();
347345
if original.delimiter.kind == tt::DelimiterKind::Invisible {

crates/hir-expand/src/name.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ pub mod known {
388388
log_syntax,
389389
module_path,
390390
option_env,
391+
quote,
391392
std_panic,
392393
stringify,
393394
trace_macros,

crates/hir/src/source_analyzer.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,9 +1162,40 @@ fn resolve_hir_path_qualifier(
11621162
resolver: &Resolver,
11631163
path: &Path,
11641164
) -> Option<PathResolution> {
1165-
resolver
1166-
.resolve_path_in_type_ns_fully(db.upcast(), &path)
1167-
.map(|ty| match ty {
1165+
(|| {
1166+
let (ty, unresolved) = match path.type_anchor() {
1167+
Some(type_ref) => {
1168+
let (_, res) =
1169+
TyLoweringContext::new_maybe_unowned(db, resolver, resolver.type_owner())
1170+
.lower_ty_ext(type_ref);
1171+
res.map(|ty_ns| (ty_ns, path.segments().first()))
1172+
}
1173+
None => {
1174+
let (ty, remaining_idx, _) = resolver.resolve_path_in_type_ns(db.upcast(), path)?;
1175+
match remaining_idx {
1176+
Some(remaining_idx) => {
1177+
if remaining_idx + 1 == path.segments().len() {
1178+
Some((ty, path.segments().last()))
1179+
} else {
1180+
None
1181+
}
1182+
}
1183+
None => Some((ty, None)),
1184+
}
1185+
}
1186+
}?;
1187+
1188+
// If we are in a TypeNs for a Trait, and we have an unresolved name, try to resolve it as a type
1189+
// within the trait's associated types.
1190+
if let (Some(unresolved), &TypeNs::TraitId(trait_id)) = (&unresolved, &ty) {
1191+
if let Some(type_alias_id) =
1192+
db.trait_data(trait_id).associated_type_by_name(unresolved.name)
1193+
{
1194+
return Some(PathResolution::Def(ModuleDefId::from(type_alias_id).into()));
1195+
}
1196+
}
1197+
1198+
let res = match ty {
11681199
TypeNs::SelfType(it) => PathResolution::SelfType(it.into()),
11691200
TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()),
11701201
TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => {
@@ -1175,11 +1206,28 @@ fn resolve_hir_path_qualifier(
11751206
TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()),
11761207
TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()),
11771208
TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()),
1178-
})
1179-
.or_else(|| {
1180-
resolver
1181-
.resolve_module_path_in_items(db.upcast(), path.mod_path()?)
1182-
.take_types()
1183-
.map(|it| PathResolution::Def(it.into()))
1184-
})
1209+
};
1210+
match unresolved {
1211+
Some(unresolved) => resolver
1212+
.generic_def()
1213+
.and_then(|def| {
1214+
hir_ty::associated_type_shorthand_candidates(
1215+
db,
1216+
def,
1217+
res.in_type_ns()?,
1218+
|name, id| (name == unresolved.name).then_some(id),
1219+
)
1220+
})
1221+
.map(TypeAlias::from)
1222+
.map(Into::into)
1223+
.map(PathResolution::Def),
1224+
None => Some(res),
1225+
}
1226+
})()
1227+
.or_else(|| {
1228+
resolver
1229+
.resolve_module_path_in_items(db.upcast(), path.mod_path()?)
1230+
.take_types()
1231+
.map(|it| PathResolution::Def(it.into()))
1232+
})
11851233
}

crates/proc-macro-api/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use triomphe::Arc;
2020
use serde::{Deserialize, Serialize};
2121

2222
use crate::{
23-
msg::{ExpandMacro, ExpnGlobals, FlatTree, PanicMessage, HAS_GLOBAL_SPANS},
23+
msg::{
24+
deserialize_span_data_index_map, flat::serialize_span_data_index_map, ExpandMacro,
25+
ExpnGlobals, FlatTree, PanicMessage, HAS_GLOBAL_SPANS, RUST_ANALYZER_SPAN_SUPPORT,
26+
},
2427
process::ProcMacroProcessSrv,
2528
};
2629

@@ -166,6 +169,11 @@ impl ProcMacro {
166169
call_site,
167170
mixed_site,
168171
},
172+
span_data_table: if version >= RUST_ANALYZER_SPAN_SUPPORT {
173+
serialize_span_data_index_map(&span_data_table)
174+
} else {
175+
Vec::new()
176+
},
169177
};
170178

171179
let response = self
@@ -178,9 +186,14 @@ impl ProcMacro {
178186
msg::Response::ExpandMacro(it) => {
179187
Ok(it.map(|tree| FlatTree::to_subtree_resolved(tree, version, &span_data_table)))
180188
}
181-
msg::Response::ListMacros(..) | msg::Response::ApiVersionCheck(..) => {
182-
Err(ServerError { message: "unexpected response".to_string(), io: None })
183-
}
189+
msg::Response::ExpandMacroExtended(it) => Ok(it.map(|resp| {
190+
FlatTree::to_subtree_resolved(
191+
resp.tree,
192+
version,
193+
&deserialize_span_data_index_map(&resp.span_data_table),
194+
)
195+
})),
196+
_ => Err(ServerError { message: "unexpected response".to_string(), io: None }),
184197
}
185198
}
186199
}

0 commit comments

Comments
 (0)