diff --git a/library/proc_macro/src/bridge/client.rs b/library/proc_macro/src/bridge/client.rs index bdaa865a998d6..40ca1e221b15b 100644 --- a/library/proc_macro/src/bridge/client.rs +++ b/library/proc_macro/src/bridge/client.rs @@ -2,9 +2,13 @@ use std::cell::RefCell; use std::marker::PhantomData; +use std::num::NonZero; use std::sync::atomic::AtomicU32; use super::*; +use crate::StandaloneLevel; +use crate::bridge::server::{Dispatcher, DispatcherTrait}; +use crate::bridge::standalone::NoRustc; macro_rules! define_client_handles { ( @@ -107,6 +111,10 @@ impl Clone for TokenStream { } impl Span { + pub(crate) fn dummy() -> Span { + Span { handle: NonZero::new(1).unwrap() } + } + pub(crate) fn def_site() -> Span { Bridge::with(|bridge| bridge.globals.def_site) } @@ -141,7 +149,10 @@ macro_rules! define_client_side { api_tags::Method::$name(api_tags::$name::$method).encode(&mut buf, &mut ()); $($arg.encode(&mut buf, &mut ());)* - buf = bridge.dispatch.call(buf); + buf = match &mut bridge.dispatch { + DispatchWay::Closure(f) => f.call(buf), + DispatchWay::Directly(disp) => disp.dispatch(buf), + }; let r = Result::<_, PanicMessage>::decode(&mut &buf[..], &mut ()); @@ -155,13 +166,18 @@ macro_rules! define_client_side { } with_api!(self, self, define_client_side); +enum DispatchWay<'a> { + Closure(closure::Closure<'a, Buffer, Buffer>), + Directly(Dispatcher), +} + struct Bridge<'a> { /// Reusable buffer (only `clear`-ed, never shrunk), primarily /// used for making requests. cached_buffer: Buffer, /// Server-side function that the client uses to make requests. - dispatch: closure::Closure<'a, Buffer, Buffer>, + dispatch: DispatchWay<'a>, /// Provided globals for this macro expansion. globals: ExpnGlobals, @@ -173,12 +189,33 @@ impl<'a> !Sync for Bridge<'a> {} #[allow(unsafe_code)] mod state { use std::cell::{Cell, RefCell}; + use std::marker::PhantomData; use std::ptr; use super::Bridge; + use crate::StandaloneLevel; + use crate::bridge::buffer::Buffer; + use crate::bridge::client::{COUNTERS, DispatchWay}; + use crate::bridge::server::{Dispatcher, HandleStore, MarkedTypes}; + use crate::bridge::{ExpnGlobals, Marked, standalone}; thread_local! { static BRIDGE_STATE: Cell<*const ()> = const { Cell::new(ptr::null()) }; + static STANDALONE: RefCell> = RefCell::new(standalone_bridge()); + pub(super) static USE_STANDALONE: Cell = const { Cell::new(StandaloneLevel::Never) }; + } + + fn standalone_bridge() -> Bridge<'static> { + let mut store = HandleStore::new(&COUNTERS); + let id = store.Span.alloc(Marked { value: standalone::Span, _marker: PhantomData }); + let dummy = super::Span { handle: id }; + let dispatcher = + Dispatcher { handle_store: store, server: MarkedTypes(standalone::NoRustc) }; + Bridge { + cached_buffer: Buffer::new(), + dispatch: DispatchWay::Directly(dispatcher), + globals: ExpnGlobals { call_site: dummy, def_site: dummy, mixed_site: dummy }, + } } pub(super) fn set<'bridge, R>(state: &RefCell>, f: impl FnOnce() -> R) -> R { @@ -199,16 +236,23 @@ mod state { pub(super) fn with( f: impl for<'bridge> FnOnce(Option<&RefCell>>) -> R, ) -> R { - let state = BRIDGE_STATE.get(); - // SAFETY: the only place where the pointer is set is in `set`. It puts - // back the previous value after the inner call has returned, so we know - // that as long as the pointer is not null, it came from a reference to - // a `RefCell` that outlasts the call to this function. Since `f` - // works the same for any lifetime of the bridge, including the actual - // one, we can lie here and say that the lifetime is `'static` without - // anyone noticing. - let bridge = unsafe { state.cast::>>().as_ref() }; - f(bridge) + let level = USE_STANDALONE.get(); + if level == StandaloneLevel::Always + || (level == StandaloneLevel::FallbackOnly && BRIDGE_STATE.get().is_null()) + { + STANDALONE.with(|bridge| f(Some(bridge))) + } else { + let state = BRIDGE_STATE.get(); + // SAFETY: the only place where the pointer is set is in `set`. It puts + // back the previous value after the inner call has returned, so we know + // that as long as the pointer is not null, it came from a reference to + // a `RefCell` that outlasts the call to this function. Since `f` + // works the same for any lifetime of the bridge, including the actual + // one, we can lie here and say that the lifetime is `'static` without + // anyone noticing. + let bridge = unsafe { state.cast::>>().as_ref() }; + f(bridge) + } } } @@ -228,6 +272,10 @@ pub(crate) fn is_available() -> bool { state::with(|s| s.is_some()) } +pub(crate) fn enable_standalone(level: StandaloneLevel) { + state::USE_STANDALONE.set(level); +} + /// A client-side RPC entry-point, which may be using a different `proc_macro` /// from the one used by the server, but can be invoked compatibly. /// @@ -292,7 +340,11 @@ fn run_client Decode<'a, 's, ()>, R: Encode<()>>( let (globals, input) = <(ExpnGlobals, A)>::decode(reader, &mut ()); // Put the buffer we used for input back in the `Bridge` for requests. - let state = RefCell::new(Bridge { cached_buffer: buf.take(), dispatch, globals }); + let state = RefCell::new(Bridge { + cached_buffer: buf.take(), + dispatch: DispatchWay::Closure(dispatch), + globals, + }); let output = state::set(&state, || f(input)); diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index b0ee9c0cc3027..4f5fe2a08b8b7 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -138,6 +138,7 @@ mod rpc; mod selfless_reify; #[forbid(unsafe_code)] pub mod server; +pub(crate) mod standalone; #[allow(unsafe_code)] mod symbol; diff --git a/library/proc_macro/src/bridge/server.rs b/library/proc_macro/src/bridge/server.rs index e9ef26c07f24f..051151b6c7c69 100644 --- a/library/proc_macro/src/bridge/server.rs +++ b/library/proc_macro/src/bridge/server.rs @@ -12,12 +12,12 @@ macro_rules! define_server_handles { ) => { #[allow(non_snake_case)] pub(super) struct HandleStore { - $($oty: handle::OwnedStore,)* - $($ity: handle::InternedStore,)* + $(pub(super) $oty: handle::OwnedStore,)* + $(pub(super) $ity: handle::InternedStore,)* } impl HandleStore { - fn new(handle_counters: &'static client::HandleCounters) -> Self { + pub(super) fn new(handle_counters: &'static client::HandleCounters) -> Self { HandleStore { $($oty: handle::OwnedStore::new(&handle_counters.$oty),)* $($ity: handle::InternedStore::new(&handle_counters.$ity),)* @@ -119,7 +119,7 @@ macro_rules! declare_server_traits { } with_api!(Self, self_, declare_server_traits); -pub(super) struct MarkedTypes(S); +pub(super) struct MarkedTypes(pub(super) S); impl Server for MarkedTypes { fn globals(&mut self) -> ExpnGlobals { @@ -150,9 +150,9 @@ macro_rules! define_mark_types_impls { } with_api!(Self, self_, define_mark_types_impls); -struct Dispatcher { - handle_store: HandleStore, - server: S, +pub(super) struct Dispatcher { + pub(super) handle_store: HandleStore>, + pub(super) server: MarkedTypes, } macro_rules! define_dispatcher_impl { @@ -167,7 +167,7 @@ macro_rules! define_dispatcher_impl { fn dispatch(&mut self, buf: Buffer) -> Buffer; } - impl DispatcherTrait for Dispatcher> { + impl DispatcherTrait for Dispatcher { $(type $name = as Types>::$name;)* fn dispatch(&mut self, mut buf: Buffer) -> Buffer { diff --git a/library/proc_macro/src/bridge/standalone.rs b/library/proc_macro/src/bridge/standalone.rs new file mode 100644 index 0000000000000..c64afd903cd30 --- /dev/null +++ b/library/proc_macro/src/bridge/standalone.rs @@ -0,0 +1,457 @@ +#![warn(warnings)] +use std::cell::{Cell, RefCell}; +use std::ops::{Bound, Range}; + +use crate::bridge::client::Symbol; +use crate::bridge::fxhash::FxHashMap; +use crate::bridge::{ + DelimSpan, Diagnostic, ExpnGlobals, Group, LitKind, Literal, Punct, TokenTree, server, +}; +use crate::{Delimiter, LEGAL_PUNCT_CHARS}; + +pub struct NoRustc; + +impl server::Span for NoRustc { + fn debug(&mut self, _: Self::Span) -> String { + "Span".to_string() + } + + fn parent(&mut self, _: Self::Span) -> Option { + None + } + + fn source(&mut self, _: Self::Span) -> Self::Span { + Span + } + + fn byte_range(&mut self, _: Self::Span) -> Range { + 0..0 + } + + fn start(&mut self, _: Self::Span) -> Self::Span { + Span + } + + fn end(&mut self, _: Self::Span) -> Self::Span { + Span + } + + fn line(&mut self, _: Self::Span) -> usize { + 1 + } + + fn column(&mut self, _: Self::Span) -> usize { + 1 + } + + fn file(&mut self, _: Self::Span) -> String { + "".to_string() + } + + fn local_file(&mut self, _: Self::Span) -> Option { + None + } + + fn join(&mut self, _: Self::Span, _: Self::Span) -> Option { + Some(Span) + } + + fn subspan( + &mut self, + _: Self::Span, + _start: Bound, + _end: Bound, + ) -> Option { + Some(Span) + } + + fn resolved_at(&mut self, _span: Self::Span, _at: Self::Span) -> Self::Span { + Span + } + + fn source_text(&mut self, _: Self::Span) -> Option { + None + } + + fn save_span(&mut self, _: Self::Span) -> usize { + let n = SAVED_SPAN_COUNT.get(); + SAVED_SPAN_COUNT.set(n + 1); + n + } + + fn recover_proc_macro_span(&mut self, id: usize) -> Self::Span { + if id < SAVED_SPAN_COUNT.get() { + Span + } else { + panic!("recovered span index out of bounds"); + } + } +} + +thread_local! { + static SAVED_SPAN_COUNT: Cell = const { Cell::new(0) }; + static TRACKED_ENV_VARS: RefCell>> = RefCell::new(FxHashMap::default()); +} + +fn parse_maybe_raw_str( + mut s: &str, + raw_variant: fn(u8) -> LitKind, + regular_variant: LitKind, +) -> Result, ()> { + /// Returns a string containing exactly `num` '#' characters. + /// Uses a 256-character source string literal which is always safe to + /// index with a `u8` index. + fn get_hashes_str(num: u8) -> &'static str { + const HASHES: &str = "\ + ################################################################\ + ################################################################\ + ################################################################\ + ################################################################\ + "; + const _: () = assert!(HASHES.len() == 256); + &HASHES[..num as usize] + } + let mut hash_count = None; + + if s.starts_with('r') { + s = s.strip_prefix('r').unwrap(); + let mut h = 0; + for c in s.chars() { + if c == '#' { + if h == u8::MAX { + return Err(()); + } + h += 1; + } else { + break; + } + } + hash_count = Some(h); + let hashes = get_hashes_str(h); + s = s.strip_prefix(hashes).unwrap(); + s = s.strip_suffix(hashes).ok_or(())?; + } + let sym = parse_plain_str(s)?; + + Ok(make_literal( + if let Some(h) = hash_count { raw_variant(h) } else { regular_variant }, + sym, + None, + )) +} + +fn parse_char(s: &str) -> Result, ()> { + if s.chars().count() == 1 { + Ok(make_literal(LitKind::Char, Symbol::new(s), None)) + } else { + Err(()) + } +} + +fn parse_plain_str(mut s: &str) -> Result { + s = s.strip_prefix("\"").ok_or(())?.strip_suffix('\"').ok_or(())?; + Ok(Symbol::new(s)) +} + +fn parse_numeral(s: &str) -> Result, ()> { + todo!() +} + +fn make_literal(kind: LitKind, symbol: Symbol, suffix: Option) -> Literal { + Literal { kind, symbol, suffix, span: Span } +} + +impl server::FreeFunctions for NoRustc { + fn injected_env_var(&mut self, var: &str) -> Option { + TRACKED_ENV_VARS.with_borrow(|vars| vars.get(var)?.clone()) + } + + fn track_env_var(&mut self, var: &str, value: Option<&str>) { + TRACKED_ENV_VARS + .with_borrow_mut(|vars| vars.insert(var.to_string(), value.map(ToString::to_string))); + } + + fn track_path(&mut self, _path: &str) {} + + fn literal_from_str(&mut self, s: &str) -> Result, ()> { + let mut chars = s.chars(); + let Some(first) = chars.next() else { + return Err(()); + }; + let rest = &s[1..]; + + match first { + 'b' => { + if chars.next() == Some('\'') { + parse_char(rest).map(|mut lit| { + lit.kind = LitKind::Byte; + lit + }) + } else { + parse_maybe_raw_str(rest, LitKind::ByteStrRaw, LitKind::ByteStr) + } + } + 'c' => parse_maybe_raw_str(rest, LitKind::CStrRaw, LitKind::CStr), + 'r' => parse_maybe_raw_str(rest, LitKind::StrRaw, LitKind::Str), + '0'..='9' | '-' => { + /*if s.ends_with("f16") + || s.ends_with("f32") + || s.ends_with("f64") + || s.ends_with("f128") + { + Literal { kind: todo!(), symbol: todo!(), suffix: todo!(), span: Span }; + } + todo!()*/ + parse_numeral(s) + } + '\'' => parse_char(s), + '"' => Ok(make_literal(LitKind::Str, parse_plain_str(s)?, None)), + _ => Err(()), + } + } + + fn emit_diagnostic(&mut self, _: Diagnostic) { + panic!("cannot emit diagnostic in standalone mode"); + } +} + +impl server::TokenStream for NoRustc { + fn is_empty(&mut self, tokens: &Self::TokenStream) -> bool { + tokens.0.is_empty() + } + + fn expand_expr(&mut self, _tokens: &Self::TokenStream) -> Result { + todo!("`expand_expr` is not yet supported in the standalone backend") + } + + fn from_str(&mut self, src: &str) -> Self::TokenStream { + /// Returns the delimiter, and whether it is the opening form. + fn char_to_delim(c: char) -> Option<(Delimiter, bool)> { + Some(match c { + '(' => (Delimiter::Parenthesis, true), + ')' => (Delimiter::Parenthesis, false), + '{' => (Delimiter::Brace, true), + '}' => (Delimiter::Brace, false), + '[' => (Delimiter::Bracket, true), + ']' => (Delimiter::Bracket, false), + _ => return None, + }) + } + + let mut unfinished_streams = vec![TokenStream::new()]; + let mut unclosed_delimiters = Vec::new(); + let mut current_ident = String::new(); + for c in src.chars() { + if let Some((delim, is_opening)) = char_to_delim(c) { + if is_opening { + unclosed_delimiters.push(delim); + unfinished_streams.push(TokenStream::new()); + } else if unclosed_delimiters.pop() == Some(delim) { + let group = TokenTree::<_, _, Symbol>::Group(Group { + delimiter: delim, + stream: unfinished_streams.pop(), + span: DelimSpan::from_single(Span), + }); + unfinished_streams.last_mut().unwrap().0.push(group); + } else { + panic!("cannot parse string into token stream") + } + } else if LEGAL_PUNCT_CHARS.contains(&c) { + unfinished_streams.last_mut().unwrap().0.push(TokenTree::Punct(Punct { + ch: c as u8, + joint: false, // TODO + span: Span, + })); + } + match c { + _ => todo!(), + } + } + unfinished_streams[0].clone() + } + + fn to_string(&mut self, tokens: &Self::TokenStream) -> String { + /* + /// Returns a string containing exactly `num` '#' characters. + /// Uses a 256-character source string literal which is always safe to + /// index with a `u8` index. + fn get_hashes_str(num: u8) -> &'static str { + const HASHES: &str = "\ + ################################################################\ + ################################################################\ + ################################################################\ + ################################################################\ + "; + const _: () = assert!(HASHES.len() == 256); + &HASHES[..num as usize] + }*/ + + let mut s = String::new(); + let mut last = String::new(); + let mut second_last = String::new(); + + for (idx, tree) in tokens.0.iter().enumerate() { + let mut space = true; + let new_part = match tree { + TokenTree::Group(group) => { + let inner = if let Some(stream) = &group.stream { + self.to_string(stream) + } else { + String::new() + }; + match group.delimiter { + Delimiter::Parenthesis => format!("({inner})"), + Delimiter::Brace => { + if inner.is_empty() { + "{ }".to_string() + } else { + format!("{{ {inner} }}") + } + } + Delimiter::Bracket => format!("[{inner}]"), + Delimiter::None => inner, + } + } + TokenTree::Ident(ident) => { + if ident.is_raw { + format!("r#{}", ident.sym) + } else { + ident.sym.to_string() + } + } + TokenTree::Literal(lit) => { + let respanned = Literal { + kind: lit.kind, + symbol: lit.symbol, + suffix: lit.suffix, + span: super::client::Span::dummy(), + }; + crate::Literal(respanned).to_string() + /*let inner = if let Some(suffix) = lit.suffix { + format!("{}{suffix}", lit.symbol) + } else { + lit.symbol.to_string() + }; + match lit.kind { + LitKind::Byte => format!("b'{inner}'"), + LitKind::ByteStr => format!("b\"{inner}\""), + LitKind::ByteStrRaw(raw) => { + format!("br{0}\"{inner}\"{0}", get_hashes_str(raw)) + } + LitKind::CStr => format!("c\"{inner}\""), + LitKind::CStrRaw(raw) => { + format!("cr{0}\"{inner}\"{0}", get_hashes_str(raw)) + } + LitKind::Char => format!("'{inner}'"), + LitKind::ErrWithGuar => unreachable!(), + LitKind::Float | LitKind::Integer => inner, + LitKind::Str => format!("\"{inner}\""), + LitKind::StrRaw(raw) => format!("r{0}\"{inner}\"{0}", get_hashes_str(raw)), + }*/ + } + TokenTree::Punct(punct) => { + let c = punct.ch as char; + if c == '\'' { + space = false; + } + c.to_string() + } + }; + + const NON_SEPARATABLE_TOKENS: &[(char, char)] = &[(':', ':'), ('-', '>'), ('=', '>')]; + + for (first, second) in NON_SEPARATABLE_TOKENS { + if second_last == first.to_string() && last == second.to_string() && new_part != ":" + { + s.pop(); // pop ' ' + s.pop(); // pop `second` + s.pop(); // pop ' ' + s.push(*second); + s.push(' '); + } + } + s.push_str(&new_part); + second_last = last; + last = new_part; + if space && idx + 1 != tokens.0.len() { + s.push(' '); + } + } + s + } + + fn from_token_tree( + &mut self, + tree: TokenTree, + ) -> Self::TokenStream { + TokenStream(vec![tree]) + } + + fn concat_trees( + &mut self, + base: Option, + trees: Vec>, + ) -> Self::TokenStream { + let mut base = base.unwrap_or_else(TokenStream::new); + base.0.extend(trees); + base + } + + fn concat_streams( + &mut self, + base: Option, + streams: Vec, + ) -> Self::TokenStream { + let mut base = base.unwrap_or_else(TokenStream::new); + for stream in streams { + base = self.concat_trees(Some(base), stream.0); + } + base + } + + fn into_trees( + &mut self, + tokens: Self::TokenStream, + ) -> Vec> { + tokens.0 + } +} + +pub struct FreeFunctions; +#[derive(Clone, Default)] +pub struct TokenStream(Vec>); +impl TokenStream { + pub fn new() -> Self { + Self(Vec::new()) + } +} + +#[derive(Hash, PartialEq, Eq, Clone, Copy)] +pub struct Span; + +impl server::Types for NoRustc { + type FreeFunctions = FreeFunctions; + type TokenStream = TokenStream; + type Span = Span; + type Symbol = Symbol; +} + +impl server::Server for NoRustc { + fn globals(&mut self) -> ExpnGlobals { + ExpnGlobals { def_site: Span, call_site: Span, mixed_site: Span } + } + + fn intern_symbol(ident: &str) -> Self::Symbol { + Symbol::new(ident) + } + + fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { + symbol.with(f); + } +} + +impl server::Symbol for NoRustc { + fn normalize_and_validate_ident(&mut self, string: &str) -> Result { + todo!() + } +} diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 1aa6064633c3b..9b0da7d482379 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -89,6 +89,28 @@ pub fn is_available() -> bool { bridge::client::is_available() } +/// Controls the extent to which the new standalone backend is used. +/// +/// When this will be stabilized, the default level will change from +/// `Never` to `FallbackOnly`. +#[unstable(feature = "proc_macro_standalone", issue = "130856")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum StandaloneLevel { + /// The standalone implementation is never used. This is the default. + Never, + /// The standalone implementation is only used outside of procedural macros. + FallbackOnly, + /// The standalone implementation is always used, even in procedural macros. + Always, +} + +/// Enables the new experimental standalone backend, which allows calling the +/// functions in this crate outside of procedural macros. +#[unstable(feature = "proc_macro_standalone", issue = "130856")] +pub fn enable_standalone(level: StandaloneLevel) { + bridge::client::enable_standalone(level); +} + /// The main type provided by this crate, representing an abstract stream of /// tokens, or, more specifically, a sequence of token trees. /// The type provides interfaces for iterating over those token trees and, conversely, @@ -947,6 +969,11 @@ pub enum Spacing { Alone, } +pub(crate) const LEGAL_PUNCT_CHARS: &[char] = &[ + '=', '<', '>', '!', '~', '+', '-', '*', '/', '%', '^', '&', '|', '@', '.', ',', ';', ':', '#', + '$', '?', '\'', +]; + impl Punct { /// Creates a new `Punct` from the given character and spacing. /// The `ch` argument must be a valid punctuation character permitted by the language, @@ -956,11 +983,7 @@ impl Punct { /// which can be further configured with the `set_span` method below. #[stable(feature = "proc_macro_lib2", since = "1.29.0")] pub fn new(ch: char, spacing: Spacing) -> Punct { - const LEGAL_CHARS: &[char] = &[ - '=', '<', '>', '!', '~', '+', '-', '*', '/', '%', '^', '&', '|', '@', '.', ',', ';', - ':', '#', '$', '?', '\'', - ]; - if !LEGAL_CHARS.contains(&ch) { + if !LEGAL_PUNCT_CHARS.contains(&ch) { panic!("unsupported character `{:?}`", ch); } Punct(bridge::Punct { @@ -1156,7 +1179,7 @@ macro_rules! unsuffixed_int_literals { /// specified on this token, meaning that invocations like /// `Literal::i8_unsuffixed(1)` are equivalent to /// `Literal::u32_unsuffixed(1)`. - /// Literals created from negative numbers might not survive rountrips through + /// Literals created from negative numbers might not survive roundtrips through /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). /// /// Literals created through this method have the `Span::call_site()` @@ -1219,7 +1242,7 @@ impl Literal { /// This constructor is similar to those like `Literal::i8_unsuffixed` where /// the float's value is emitted directly into the token but no suffix is /// used, so it may be inferred to be a `f64` later in the compiler. - /// Literals created from negative numbers might not survive rountrips through + /// Literals created from negative numbers might not survive roundtrips through /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). /// /// # Panics @@ -1244,7 +1267,7 @@ impl Literal { /// specified is the preceding part of the token and `f32` is the suffix of /// the token. This token will always be inferred to be an `f32` in the /// compiler. - /// Literals created from negative numbers might not survive rountrips through + /// Literals created from negative numbers might not survive roundtrips through /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). /// /// # Panics @@ -1264,7 +1287,7 @@ impl Literal { /// This constructor is similar to those like `Literal::i8_unsuffixed` where /// the float's value is emitted directly into the token but no suffix is /// used, so it may be inferred to be a `f64` later in the compiler. - /// Literals created from negative numbers might not survive rountrips through + /// Literals created from negative numbers might not survive roundtrips through /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). /// /// # Panics @@ -1289,7 +1312,7 @@ impl Literal { /// specified is the preceding part of the token and `f64` is the suffix of /// the token. This token will always be inferred to be an `f64` in the /// compiler. - /// Literals created from negative numbers might not survive rountrips through + /// Literals created from negative numbers might not survive roundtrips through /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). /// /// # Panics