about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-02-13 10:41:36 +0000
committerbors <bors@rust-lang.org>2024-02-13 10:41:36 +0000
commit925705e0c920836fefe617c4caf9e8aa4b2bfaf0 (patch)
tree13c23c424a23554614f0a975a313a5363ba189e0
parenta9800004cb3adcc79530a695a3d90270c3c9e456 (diff)
parent4923b8a74bdffb0a8d1ce3760d00da8959a711c2 (diff)
downloadrust-925705e0c920836fefe617c4caf9e8aa4b2bfaf0.tar.gz
rust-925705e0c920836fefe617c4caf9e8aa4b2bfaf0.zip
Auto merge of #16446 - Tyrubias:literal_from_str, r=Veykril
Implement `literal_from_str` for proc macro server

Closes #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`
-rw-r--r--Cargo.lock1
-rw-r--r--crates/proc-macro-srv/Cargo.toml1
-rw-r--r--crates/proc-macro-srv/src/server.rs27
-rw-r--r--crates/proc-macro-srv/src/server/rust_analyzer_span.rs35
-rw-r--r--crates/proc-macro-srv/src/server/token_id.rs35
-rw-r--r--crates/syntax/src/ast/token_ext.rs10
-rw-r--r--crates/syntax/src/lib.rs22
7 files changed, 119 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 49f39537c01..0fdb366c1f9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1331,6 +1331,7 @@ dependencies = [
  "proc-macro-test",
  "span",
  "stdx",
+ "syntax",
  "tt",
 ]
 
diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml
index ba17ea6f7b4..d0cdc51c3c2 100644
--- a/crates/proc-macro-srv/Cargo.toml
+++ b/crates/proc-macro-srv/Cargo.toml
@@ -29,6 +29,7 @@ paths.workspace = true
 base-db.workspace = true
 span.workspace = true
 proc-macro-api.workspace = true
+syntax.workspace = true
 
 [dev-dependencies]
 expect-test = "1.4.0"
diff --git a/crates/proc-macro-srv/src/server.rs b/crates/proc-macro-srv/src/server.rs
index ff8fd295d88..bb49dc14f96 100644
--- a/crates/proc-macro-srv/src/server.rs
+++ b/crates/proc-macro-srv/src/server.rs
@@ -17,6 +17,7 @@ pub mod rust_analyzer_span;
 mod symbol;
 pub mod token_id;
 pub use symbol::*;
+use syntax::ast::{self, IsString};
 use tt::Spacing;
 
 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 {
     }
 }
 
+fn literal_to_external(literal_kind: ast::LiteralKind) -> Option<proc_macro::bridge::LitKind> {
+    match literal_kind {
+        ast::LiteralKind::String(data) => Some(if data.is_raw() {
+            bridge::LitKind::StrRaw(data.raw_delimiter_count()?)
+        } else {
+            bridge::LitKind::Str
+        }),
+
+        ast::LiteralKind::ByteString(data) => Some(if data.is_raw() {
+            bridge::LitKind::ByteStrRaw(data.raw_delimiter_count()?)
+        } else {
+            bridge::LitKind::ByteStr
+        }),
+        ast::LiteralKind::CString(data) => Some(if data.is_raw() {
+            bridge::LitKind::CStrRaw(data.raw_delimiter_count()?)
+        } else {
+            bridge::LitKind::CStr
+        }),
+        ast::LiteralKind::IntNumber(_) => Some(bridge::LitKind::Integer),
+        ast::LiteralKind::FloatNumber(_) => Some(bridge::LitKind::Float),
+        ast::LiteralKind::Char(_) => Some(bridge::LitKind::Char),
+        ast::LiteralKind::Byte(_) => Some(bridge::LitKind::Byte),
+        ast::LiteralKind::Bool(_) => None,
+    }
+}
+
 struct LiteralFormatter<S>(bridge::Literal<S, Symbol>);
 
 impl<S> LiteralFormatter<S> {
diff --git a/crates/proc-macro-srv/src/server/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server/rust_analyzer_span.rs
index c7c7bea9941..cf6e816d599 100644
--- a/crates/proc-macro-srv/src/server/rust_analyzer_span.rs
+++ b/crates/proc-macro-srv/src/server/rust_analyzer_span.rs
@@ -13,10 +13,11 @@ use std::{
 use ::tt::{TextRange, TextSize};
 use proc_macro::bridge::{self, server};
 use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
+use syntax::ast::{self, IsString};
 
 use crate::server::{
-    delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter,
-    Symbol, SymbolInternerRef, SYMBOL_INTERNER,
+    delim_to_external, delim_to_internal, literal_to_external, token_stream::TokenStreamBuilder,
+    LiteralFormatter, Symbol, SymbolInternerRef, SYMBOL_INTERNER,
 };
 mod tt {
     pub use ::tt::*;
@@ -70,11 +71,33 @@ impl server::FreeFunctions for RaSpanServer {
         &mut self,
         s: &str,
     ) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> {
-        // FIXME: keep track of LitKind and Suffix
+        let literal = ast::Literal::parse(s).ok_or(())?;
+        let literal = literal.tree();
+
+        let kind = literal_to_external(literal.kind()).ok_or(())?;
+
+        // FIXME: handle more than just int and float suffixes
+        let suffix = match literal.kind() {
+            ast::LiteralKind::FloatNumber(num) => num.suffix().map(ToString::to_string),
+            ast::LiteralKind::IntNumber(num) => num.suffix().map(ToString::to_string),
+            _ => None,
+        };
+
+        let text = match literal.kind() {
+            ast::LiteralKind::String(data) => data.text_without_quotes().to_string(),
+            ast::LiteralKind::ByteString(data) => data.text_without_quotes().to_string(),
+            ast::LiteralKind::CString(data) => data.text_without_quotes().to_string(),
+            _ => s.to_string(),
+        };
+        let text = if let Some(ref suffix) = suffix { text.strip_suffix(suffix) } else { None }
+            .unwrap_or(&text);
+
+        let suffix = suffix.map(|suffix| Symbol::intern(self.interner, &suffix));
+
         Ok(bridge::Literal {
-            kind: bridge::LitKind::Err,
-            symbol: Symbol::intern(self.interner, s),
-            suffix: None,
+            kind,
+            symbol: Symbol::intern(self.interner, text),
+            suffix,
             span: self.call_site,
         })
     }
diff --git a/crates/proc-macro-srv/src/server/token_id.rs b/crates/proc-macro-srv/src/server/token_id.rs
index edbdc67b482..70e577f576f 100644
--- a/crates/proc-macro-srv/src/server/token_id.rs
+++ b/crates/proc-macro-srv/src/server/token_id.rs
@@ -6,10 +6,11 @@ use std::{
 };
 
 use proc_macro::bridge::{self, server};
+use syntax::ast::{self, IsString};
 
 use crate::server::{
-    delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter,
-    Symbol, SymbolInternerRef, SYMBOL_INTERNER,
+    delim_to_external, delim_to_internal, literal_to_external, token_stream::TokenStreamBuilder,
+    LiteralFormatter, Symbol, SymbolInternerRef, SYMBOL_INTERNER,
 };
 mod tt {
     pub use proc_macro_api::msg::TokenId;
@@ -62,11 +63,33 @@ impl server::FreeFunctions for TokenIdServer {
         &mut self,
         s: &str,
     ) -> Result<bridge::Literal<Self::Span, Self::Symbol>, ()> {
-        // FIXME: keep track of LitKind and Suffix
+        let literal = ast::Literal::parse(s).ok_or(())?;
+        let literal = literal.tree();
+
+        let kind = literal_to_external(literal.kind()).ok_or(())?;
+
+        // FIXME: handle more than just int and float suffixes
+        let suffix = match literal.kind() {
+            ast::LiteralKind::FloatNumber(num) => num.suffix().map(ToString::to_string),
+            ast::LiteralKind::IntNumber(num) => num.suffix().map(ToString::to_string),
+            _ => None,
+        };
+
+        let text = match literal.kind() {
+            ast::LiteralKind::String(data) => data.text_without_quotes().to_string(),
+            ast::LiteralKind::ByteString(data) => data.text_without_quotes().to_string(),
+            ast::LiteralKind::CString(data) => data.text_without_quotes().to_string(),
+            _ => s.to_string(),
+        };
+        let text = if let Some(ref suffix) = suffix { text.strip_suffix(suffix) } else { None }
+            .unwrap_or(&text);
+
+        let suffix = suffix.map(|suffix| Symbol::intern(self.interner, &suffix));
+
         Ok(bridge::Literal {
-            kind: bridge::LitKind::Err,
-            symbol: Symbol::intern(self.interner, s),
-            suffix: None,
+            kind,
+            symbol: Symbol::intern(self.interner, text),
+            suffix,
             span: self.call_site,
         })
     }
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 7cd1f1550b9..c93391a9792 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -204,6 +204,16 @@ pub trait IsString: AstToken {
         assert!(TextRange::up_to(contents_range.len()).contains_range(range));
         Some(range + contents_range.start())
     }
+    fn raw_delimiter_count(&self) -> Option<u8> {
+        let text = self.text();
+        let quote_range = self.text_range_between_quotes()?;
+        let range_start = self.syntax().text_range().start();
+        text[TextRange::up_to((quote_range - range_start).start())]
+            .matches('#')
+            .count()
+            .try_into()
+            .ok()
+    }
 }
 
 impl IsString for ast::String {
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index b755de86d32..f562da15037 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -182,6 +182,28 @@ impl SourceFile {
     }
 }
 
+impl ast::Literal {
+    pub fn parse(text: &str) -> Option<Parse<ast::Literal>> {
+        let lexed = parser::LexedStr::new(text);
+        let parser_input = lexed.to_input();
+        let parser_output = parser::TopEntryPoint::Expr.parse(&parser_input);
+        let (green, mut errors, _) = parsing::build_tree(lexed, parser_output);
+        let root = SyntaxNode::new_root(green.clone());
+
+        errors.extend(validation::validate(&root));
+
+        if root.kind() == SyntaxKind::LITERAL {
+            Some(Parse {
+                green,
+                errors: if errors.is_empty() { None } else { Some(errors.into()) },
+                _ty: PhantomData,
+            })
+        } else {
+            None
+        }
+    }
+}
+
 impl ast::TokenTree {
     pub fn reparse_as_comma_separated_expr(self) -> Parse<ast::MacroEagerInput> {
         let tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);