Skip to content

Commit 925705e

Browse files
committed
Auto merge of rust-lang#16446 - Tyrubias:literal_from_str, r=Veykril
Implement `literal_from_str` for proc macro server Closes rust-lang#16233 Todos and unanswered questions: - [x] Is this the correct approach? Can both the legacy and `rust_analyzer_span` servers depend on the `syntax` crate? - [ ] How should we handle suffixes for string literals? It doesn't seem like `rust-analyzer` preservers suffix information after parsing. - [x] Why are the `expect` tests failing? Specifically `test_fn_like_macro_clone_literals`
2 parents a980000 + 4923b8a commit 925705e

File tree

7 files changed

+119
-12
lines changed

7 files changed

+119
-12
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/proc-macro-srv/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ paths.workspace = true
2929
base-db.workspace = true
3030
span.workspace = true
3131
proc-macro-api.workspace = true
32+
syntax.workspace = true
3233

3334
[dev-dependencies]
3435
expect-test = "1.4.0"

crates/proc-macro-srv/src/server.rs

+27
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub mod rust_analyzer_span;
1717
mod symbol;
1818
pub mod token_id;
1919
pub use symbol::*;
20+
use syntax::ast::{self, IsString};
2021
use tt::Spacing;
2122

2223
fn delim_to_internal<S>(d: proc_macro::Delimiter, span: bridge::DelimSpan<S>) -> tt::Delimiter<S> {
@@ -54,6 +55,32 @@ fn spacing_to_external(spacing: Spacing) -> proc_macro::Spacing {
5455
}
5556
}
5657

58+
fn literal_to_external(literal_kind: ast::LiteralKind) -> Option<proc_macro::bridge::LitKind> {
59+
match literal_kind {
60+
ast::LiteralKind::String(data) => Some(if data.is_raw() {
61+
bridge::LitKind::StrRaw(data.raw_delimiter_count()?)
62+
} else {
63+
bridge::LitKind::Str
64+
}),
65+
66+
ast::LiteralKind::ByteString(data) => Some(if data.is_raw() {
67+
bridge::LitKind::ByteStrRaw(data.raw_delimiter_count()?)
68+
} else {
69+
bridge::LitKind::ByteStr
70+
}),
71+
ast::LiteralKind::CString(data) => Some(if data.is_raw() {
72+
bridge::LitKind::CStrRaw(data.raw_delimiter_count()?)
73+
} else {
74+
bridge::LitKind::CStr
75+
}),
76+
ast::LiteralKind::IntNumber(_) => Some(bridge::LitKind::Integer),
77+
ast::LiteralKind::FloatNumber(_) => Some(bridge::LitKind::Float),
78+
ast::LiteralKind::Char(_) => Some(bridge::LitKind::Char),
79+
ast::LiteralKind::Byte(_) => Some(bridge::LitKind::Byte),
80+
ast::LiteralKind::Bool(_) => None,
81+
}
82+
}
83+
5784
struct LiteralFormatter<S>(bridge::Literal<S, Symbol>);
5885

5986
impl<S> LiteralFormatter<S> {

crates/proc-macro-srv/src/server/rust_analyzer_span.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ use std::{
1313
use ::tt::{TextRange, TextSize};
1414
use proc_macro::bridge::{self, server};
1515
use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
16+
use syntax::ast::{self, IsString};
1617

1718
use crate::server::{
18-
delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter,
19-
Symbol, SymbolInternerRef, SYMBOL_INTERNER,
19+
delim_to_external, delim_to_internal, literal_to_external, token_stream::TokenStreamBuilder,
20+
LiteralFormatter, Symbol, SymbolInternerRef, SYMBOL_INTERNER,
2021
};
2122
mod tt {
2223
pub use ::tt::*;
@@ -70,11 +71,33 @@ impl server::FreeFunctions for RaSpanServer {
7071
&mut self,
7172
s: &str,
7273
) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> {
73-
// FIXME: keep track of LitKind and Suffix
74+
let literal = ast::Literal::parse(s).ok_or(())?;
75+
let literal = literal.tree();
76+
77+
let kind = literal_to_external(literal.kind()).ok_or(())?;
78+
79+
// FIXME: handle more than just int and float suffixes
80+
let suffix = match literal.kind() {
81+
ast::LiteralKind::FloatNumber(num) => num.suffix().map(ToString::to_string),
82+
ast::LiteralKind::IntNumber(num) => num.suffix().map(ToString::to_string),
83+
_ => None,
84+
};
85+
86+
let text = match literal.kind() {
87+
ast::LiteralKind::String(data) => data.text_without_quotes().to_string(),
88+
ast::LiteralKind::ByteString(data) => data.text_without_quotes().to_string(),
89+
ast::LiteralKind::CString(data) => data.text_without_quotes().to_string(),
90+
_ => s.to_string(),
91+
};
92+
let text = if let Some(ref suffix) = suffix { text.strip_suffix(suffix) } else { None }
93+
.unwrap_or(&text);
94+
95+
let suffix = suffix.map(|suffix| Symbol::intern(self.interner, &suffix));
96+
7497
Ok(bridge::Literal {
75-
kind: bridge::LitKind::Err,
76-
symbol: Symbol::intern(self.interner, s),
77-
suffix: None,
98+
kind,
99+
symbol: Symbol::intern(self.interner, text),
100+
suffix,
78101
span: self.call_site,
79102
})
80103
}

crates/proc-macro-srv/src/server/token_id.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use std::{
66
};
77

88
use proc_macro::bridge::{self, server};
9+
use syntax::ast::{self, IsString};
910

1011
use crate::server::{
11-
delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter,
12-
Symbol, SymbolInternerRef, SYMBOL_INTERNER,
12+
delim_to_external, delim_to_internal, literal_to_external, token_stream::TokenStreamBuilder,
13+
LiteralFormatter, Symbol, SymbolInternerRef, SYMBOL_INTERNER,
1314
};
1415
mod tt {
1516
pub use proc_macro_api::msg::TokenId;
@@ -62,11 +63,33 @@ impl server::FreeFunctions for TokenIdServer {
6263
&mut self,
6364
s: &str,
6465
) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> {
65-
// FIXME: keep track of LitKind and Suffix
66+
let literal = ast::Literal::parse(s).ok_or(())?;
67+
let literal = literal.tree();
68+
69+
let kind = literal_to_external(literal.kind()).ok_or(())?;
70+
71+
// FIXME: handle more than just int and float suffixes
72+
let suffix = match literal.kind() {
73+
ast::LiteralKind::FloatNumber(num) => num.suffix().map(ToString::to_string),
74+
ast::LiteralKind::IntNumber(num) => num.suffix().map(ToString::to_string),
75+
_ => None,
76+
};
77+
78+
let text = match literal.kind() {
79+
ast::LiteralKind::String(data) => data.text_without_quotes().to_string(),
80+
ast::LiteralKind::ByteString(data) => data.text_without_quotes().to_string(),
81+
ast::LiteralKind::CString(data) => data.text_without_quotes().to_string(),
82+
_ => s.to_string(),
83+
};
84+
let text = if let Some(ref suffix) = suffix { text.strip_suffix(suffix) } else { None }
85+
.unwrap_or(&text);
86+
87+
let suffix = suffix.map(|suffix| Symbol::intern(self.interner, &suffix));
88+
6689
Ok(bridge::Literal {
67-
kind: bridge::LitKind::Err,
68-
symbol: Symbol::intern(self.interner, s),
69-
suffix: None,
90+
kind,
91+
symbol: Symbol::intern(self.interner, text),
92+
suffix,
7093
span: self.call_site,
7194
})
7295
}

crates/syntax/src/ast/token_ext.rs

+10
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ pub trait IsString: AstToken {
204204
assert!(TextRange::up_to(contents_range.len()).contains_range(range));
205205
Some(range + contents_range.start())
206206
}
207+
fn raw_delimiter_count(&self) -> Option<u8> {
208+
let text = self.text();
209+
let quote_range = self.text_range_between_quotes()?;
210+
let range_start = self.syntax().text_range().start();
211+
text[TextRange::up_to((quote_range - range_start).start())]
212+
.matches('#')
213+
.count()
214+
.try_into()
215+
.ok()
216+
}
207217
}
208218

209219
impl IsString for ast::String {

crates/syntax/src/lib.rs

+22
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,28 @@ impl SourceFile {
182182
}
183183
}
184184

185+
impl ast::Literal {
186+
pub fn parse(text: &str) -> Option<Parse<ast::Literal>> {
187+
let lexed = parser::LexedStr::new(text);
188+
let parser_input = lexed.to_input();
189+
let parser_output = parser::TopEntryPoint::Expr.parse(&parser_input);
190+
let (green, mut errors, _) = parsing::build_tree(lexed, parser_output);
191+
let root = SyntaxNode::new_root(green.clone());
192+
193+
errors.extend(validation::validate(&root));
194+
195+
if root.kind() == SyntaxKind::LITERAL {
196+
Some(Parse {
197+
green,
198+
errors: if errors.is_empty() { None } else { Some(errors.into()) },
199+
_ty: PhantomData,
200+
})
201+
} else {
202+
None
203+
}
204+
}
205+
}
206+
185207
impl ast::TokenTree {
186208
pub fn reparse_as_comma_separated_expr(self) -> Parse<ast::MacroEagerInput> {
187209
let tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);

0 commit comments

Comments
 (0)