Auto merge of #14432 - Veykril:proc-macro-srv, r=lnicola
Drop support for non-syroot proc macro ABIs This makes some bigger changes to how we handle the proc-macro-srv things, for one it is now an empty crate if built without the `sysroot-abi` feature, this simplifies some things dropping the need to put the feature cfg in various places. The cli wrapper now actually depends on the server, instead of being part of the server that is just exported, that way we can have a true dummy server that just errors on each request if no sysroot support was specified.
This commit is contained in:
commit
e3e324d830
9
.github/workflows/ci.yaml
vendored
9
.github/workflows/ci.yaml
vendored
@ -24,6 +24,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CC: deny_c
|
||||
# we want to build r-a on stable to check that it keeps building on stable,
|
||||
# but we also want to test our proc-macro-srv which depends on nightly features
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -50,15 +53,15 @@ jobs:
|
||||
run: sed -i '/\[profile.dev]/a opt-level=1' Cargo.toml
|
||||
|
||||
- name: Compile (tests)
|
||||
run: cargo test --no-run --locked
|
||||
run: cargo test --no-run --locked --features sysroot-abi
|
||||
|
||||
# It's faster to `test` before `build` ¯\_(ツ)_/¯
|
||||
- name: Compile (rust-analyzer)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: cargo build --quiet
|
||||
run: cargo build --quiet --features sysroot-abi
|
||||
|
||||
- name: Test
|
||||
run: cargo test -- --nocapture --quiet
|
||||
run: cargo test --features sysroot-abi -- --nocapture --quiet
|
||||
|
||||
- name: Run analysis-stats on rust-analyzer
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1250,6 +1250,7 @@ dependencies = [
|
||||
name = "proc-macro-srv-cli"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"proc-macro-api",
|
||||
"proc-macro-srv",
|
||||
]
|
||||
|
||||
@ -1457,7 +1458,7 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"parking_lot_core 0.9.6",
|
||||
"proc-macro-api",
|
||||
"proc-macro-srv",
|
||||
"proc-macro-srv-cli",
|
||||
"profile",
|
||||
"project-model",
|
||||
"rayon",
|
||||
|
@ -10,6 +10,7 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-srv.workspace = true
|
||||
proc-macro-api.workspace = true
|
||||
|
||||
[features]
|
||||
sysroot-abi = ["proc-macro-srv/sysroot-abi"]
|
||||
|
@ -3,10 +3,9 @@
|
||||
|
||||
use proc_macro_api::msg::{self, Message};
|
||||
|
||||
use crate::ProcMacroSrv;
|
||||
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
pub fn run() -> io::Result<()> {
|
||||
let mut srv = ProcMacroSrv::default();
|
||||
let mut srv = proc_macro_srv::ProcMacroSrv::default();
|
||||
let mut buf = String::new();
|
||||
|
||||
while let Some(req) = read_request(&mut buf)? {
|
||||
@ -24,6 +23,27 @@ pub fn run() -> io::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "sysroot-abi"))]
|
||||
pub fn run() -> io::Result<()> {
|
||||
let mut buf = String::new();
|
||||
|
||||
while let Some(req) = read_request(&mut buf)? {
|
||||
let res = match req {
|
||||
msg::Request::ListMacros { .. } => {
|
||||
msg::Response::ListMacros(Err("server is built without sysroot support".to_owned()))
|
||||
}
|
||||
msg::Request::ExpandMacro(..) => msg::Response::ExpandMacro(Err(msg::PanicMessage(
|
||||
"server is built without sysroot support".to_owned(),
|
||||
))),
|
||||
msg::Request::ApiVersionCheck {} => {
|
||||
msg::Response::ApiVersionCheck(proc_macro_api::msg::CURRENT_API_VERSION)
|
||||
}
|
||||
};
|
||||
write_response(res)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_request(buf: &mut String) -> io::Result<Option<msg::Request>> {
|
||||
msg::Request::read(&mut io::stdin().lock(), buf)
|
@ -1,6 +1,5 @@
|
||||
//! A standalone binary for `proc-macro-srv`.
|
||||
|
||||
use proc_macro_srv::cli;
|
||||
//! Driver for proc macro server
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let v = std::env::var("RUST_ANALYZER_INTERNALS_DO_NOT_USE");
|
||||
@ -15,5 +14,5 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
cli::run()
|
||||
proc_macro_srv_cli::run()
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
//! Macro ABI for version 1.63 of rustc
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[doc(hidden)]
|
||||
mod proc_macro;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[doc(hidden)]
|
||||
mod ra_server;
|
||||
|
||||
use libloading::Library;
|
||||
use proc_macro_api::ProcMacroKind;
|
||||
|
||||
use super::tt;
|
||||
use super::PanicMessage;
|
||||
|
||||
pub use ra_server::TokenStream;
|
||||
|
||||
pub(crate) struct Abi {
|
||||
exported_macros: Vec<proc_macro::bridge::client::ProcMacro>,
|
||||
}
|
||||
|
||||
impl From<proc_macro::bridge::PanicMessage> for PanicMessage {
|
||||
fn from(p: proc_macro::bridge::PanicMessage) -> Self {
|
||||
Self { message: p.as_str().map(|s| s.to_string()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Abi {
|
||||
pub unsafe fn from_lib(lib: &Library, symbol_name: String) -> Result<Abi, libloading::Error> {
|
||||
let macros: libloading::Symbol<'_, &&[proc_macro::bridge::client::ProcMacro]> =
|
||||
lib.get(symbol_name.as_bytes())?;
|
||||
Ok(Self { exported_macros: macros.to_vec() })
|
||||
}
|
||||
|
||||
pub fn expand(
|
||||
&self,
|
||||
macro_name: &str,
|
||||
macro_body: &tt::Subtree,
|
||||
attributes: Option<&tt::Subtree>,
|
||||
) -> Result<tt::Subtree, PanicMessage> {
|
||||
let parsed_body = TokenStream::with_subtree(macro_body.clone());
|
||||
|
||||
let parsed_attributes =
|
||||
attributes.map_or(TokenStream::new(), |attr| TokenStream::with_subtree(attr.clone()));
|
||||
|
||||
for proc_macro in &self.exported_macros {
|
||||
match proc_macro {
|
||||
proc_macro::bridge::client::ProcMacro::CustomDerive {
|
||||
trait_name, client, ..
|
||||
} if *trait_name == macro_name => {
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Bang { name, client }
|
||||
if *name == macro_name =>
|
||||
{
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Attr { name, client }
|
||||
if *name == macro_name =>
|
||||
{
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
parsed_attributes,
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Err(proc_macro::bridge::PanicMessage::String("Nothing to expand".to_string()).into())
|
||||
}
|
||||
|
||||
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
self.exported_macros
|
||||
.iter()
|
||||
.map(|proc_macro| match proc_macro {
|
||||
proc_macro::bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
|
||||
(trait_name.to_string(), ProcMacroKind::CustomDerive)
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Bang { name, .. } => {
|
||||
(name.to_string(), ProcMacroKind::FuncLike)
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Attr { name, .. } => {
|
||||
(name.to_string(), ProcMacroKind::Attr)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
//! Buffer management for same-process client<->server communication.
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::slice;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Buffer {
|
||||
data: *mut u8,
|
||||
len: usize,
|
||||
capacity: usize,
|
||||
reserve: extern "C" fn(Buffer, usize) -> Buffer,
|
||||
drop: extern "C" fn(Buffer),
|
||||
}
|
||||
|
||||
unsafe impl Sync for Buffer {}
|
||||
unsafe impl Send for Buffer {}
|
||||
|
||||
impl Default for Buffer {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::from(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Buffer {
|
||||
type Target = [u8];
|
||||
#[inline]
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts(self.data as *const u8, self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Buffer {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe { slice::from_raw_parts_mut(self.data, self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
#[inline]
|
||||
pub(super) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn clear(&mut self) {
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn take(&mut self) -> Self {
|
||||
mem::take(self)
|
||||
}
|
||||
|
||||
// We have the array method separate from extending from a slice. This is
|
||||
// because in the case of small arrays, codegen can be more efficient
|
||||
// (avoiding a memmove call). With extend_from_slice, LLVM at least
|
||||
// currently is not able to make that optimization.
|
||||
#[inline]
|
||||
pub(super) fn extend_from_array<const N: usize>(&mut self, xs: &[u8; N]) {
|
||||
if xs.len() > (self.capacity - self.len) {
|
||||
let b = self.take();
|
||||
*self = (b.reserve)(b, xs.len());
|
||||
}
|
||||
unsafe {
|
||||
xs.as_ptr().copy_to_nonoverlapping(self.data.add(self.len), xs.len());
|
||||
self.len += xs.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn extend_from_slice(&mut self, xs: &[u8]) {
|
||||
if xs.len() > (self.capacity - self.len) {
|
||||
let b = self.take();
|
||||
*self = (b.reserve)(b, xs.len());
|
||||
}
|
||||
unsafe {
|
||||
xs.as_ptr().copy_to_nonoverlapping(self.data.add(self.len), xs.len());
|
||||
self.len += xs.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn push(&mut self, v: u8) {
|
||||
// The code here is taken from Vec::push, and we know that reserve()
|
||||
// will panic if we're exceeding isize::MAX bytes and so there's no need
|
||||
// to check for overflow.
|
||||
if self.len == self.capacity {
|
||||
let b = self.take();
|
||||
*self = (b.reserve)(b, 1);
|
||||
}
|
||||
unsafe {
|
||||
*self.data.add(self.len) = v;
|
||||
self.len += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Buffer {
|
||||
#[inline]
|
||||
fn write(&mut self, xs: &[u8]) -> io::Result<usize> {
|
||||
self.extend_from_slice(xs);
|
||||
Ok(xs.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_all(&mut self, xs: &[u8]) -> io::Result<()> {
|
||||
self.extend_from_slice(xs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Buffer {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
let b = self.take();
|
||||
(b.drop)(b);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Buffer {
|
||||
fn from(mut v: Vec<u8>) -> Self {
|
||||
let (data, len, capacity) = (v.as_mut_ptr(), v.len(), v.capacity());
|
||||
mem::forget(v);
|
||||
|
||||
// This utility function is nested in here because it can *only*
|
||||
// be safely called on `Buffer`s created by *this* `proc_macro`.
|
||||
fn to_vec(b: Buffer) -> Vec<u8> {
|
||||
unsafe {
|
||||
let Buffer { data, len, capacity, .. } = b;
|
||||
mem::forget(b);
|
||||
Vec::from_raw_parts(data, len, capacity)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn reserve(b: Buffer, additional: usize) -> Buffer {
|
||||
let mut v = to_vec(b);
|
||||
v.reserve(additional);
|
||||
Buffer::from(v)
|
||||
}
|
||||
|
||||
extern "C" fn drop(b: Buffer) {
|
||||
mem::drop(to_vec(b));
|
||||
}
|
||||
|
||||
Buffer { data, len, capacity, reserve, drop }
|
||||
}
|
||||
}
|
@ -1,510 +0,0 @@
|
||||
//! Client-side types.
|
||||
|
||||
use super::*;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
macro_rules! define_handles {
|
||||
(
|
||||
'owned: $($oty:ident,)*
|
||||
'interned: $($ity:ident,)*
|
||||
) => {
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct HandleCounters {
|
||||
$($oty: AtomicUsize,)*
|
||||
$($ity: AtomicUsize,)*
|
||||
}
|
||||
|
||||
impl HandleCounters {
|
||||
// FIXME(eddyb) use a reference to the `static COUNTERS`, instead of
|
||||
// a wrapper `fn` pointer, once `const fn` can reference `static`s.
|
||||
extern "C" fn get() -> &'static Self {
|
||||
static COUNTERS: HandleCounters = HandleCounters {
|
||||
$($oty: AtomicUsize::new(1),)*
|
||||
$($ity: AtomicUsize::new(1),)*
|
||||
};
|
||||
&COUNTERS
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`.
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct HandleStore<S: server::Types> {
|
||||
$($oty: handle::OwnedStore<S::$oty>,)*
|
||||
$($ity: handle::InternedStore<S::$ity>,)*
|
||||
}
|
||||
|
||||
impl<S: server::Types> HandleStore<S> {
|
||||
pub(super) fn new(handle_counters: &'static HandleCounters) -> Self {
|
||||
HandleStore {
|
||||
$($oty: handle::OwnedStore::new(&handle_counters.$oty),)*
|
||||
$($ity: handle::InternedStore::new(&handle_counters.$ity),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[repr(C)]
|
||||
pub(crate) struct $oty {
|
||||
handle: handle::Handle,
|
||||
// Prevent Send and Sync impls. `!Send`/`!Sync` is the usual
|
||||
// way of doing this, but that requires unstable features.
|
||||
// rust-analyzer uses this code and avoids unstable features.
|
||||
_marker: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
// Forward `Drop::drop` to the inherent `drop` method.
|
||||
impl Drop for $oty {
|
||||
fn drop(&mut self) {
|
||||
$oty {
|
||||
handle: self.handle,
|
||||
_marker: PhantomData,
|
||||
}.drop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for $oty {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
let handle = self.handle;
|
||||
mem::forget(self);
|
||||
handle.encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: server::Types> DecodeMut<'_, '_, HandleStore<server::MarkedTypes<S>>>
|
||||
for Marked<S::$oty, $oty>
|
||||
{
|
||||
fn decode(r: &mut Reader<'_>, s: &mut HandleStore<server::MarkedTypes<S>>) -> Self {
|
||||
s.$oty.take(handle::Handle::decode(r, &mut ()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for &$oty {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.handle.encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, S: server::Types> Decode<'_, 's, HandleStore<server::MarkedTypes<S>>>
|
||||
for &'s Marked<S::$oty, $oty>
|
||||
{
|
||||
fn decode(r: &mut Reader<'_>, s: &'s HandleStore<server::MarkedTypes<S>>) -> Self {
|
||||
&s.$oty[handle::Handle::decode(r, &mut ())]
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for &mut $oty {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.handle.encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, S: server::Types> DecodeMut<'_, 's, HandleStore<server::MarkedTypes<S>>>
|
||||
for &'s mut Marked<S::$oty, $oty>
|
||||
{
|
||||
fn decode(
|
||||
r: &mut Reader<'_>,
|
||||
s: &'s mut HandleStore<server::MarkedTypes<S>>
|
||||
) -> Self {
|
||||
&mut s.$oty[handle::Handle::decode(r, &mut ())]
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: server::Types> Encode<HandleStore<server::MarkedTypes<S>>>
|
||||
for Marked<S::$oty, $oty>
|
||||
{
|
||||
fn encode(self, w: &mut Writer, s: &mut HandleStore<server::MarkedTypes<S>>) {
|
||||
s.$oty.alloc(self).encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for $oty {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
$oty {
|
||||
handle: handle::Handle::decode(r, s),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
$(
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct $ity {
|
||||
handle: handle::Handle,
|
||||
// Prevent Send and Sync impls. `!Send`/`!Sync` is the usual
|
||||
// way of doing this, but that requires unstable features.
|
||||
// rust-analyzer uses this code and avoids unstable features.
|
||||
_marker: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for $ity {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.handle.encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: server::Types> DecodeMut<'_, '_, HandleStore<server::MarkedTypes<S>>>
|
||||
for Marked<S::$ity, $ity>
|
||||
{
|
||||
fn decode(r: &mut Reader<'_>, s: &mut HandleStore<server::MarkedTypes<S>>) -> Self {
|
||||
s.$ity.copy(handle::Handle::decode(r, &mut ()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: server::Types> Encode<HandleStore<server::MarkedTypes<S>>>
|
||||
for Marked<S::$ity, $ity>
|
||||
{
|
||||
fn encode(self, w: &mut Writer, s: &mut HandleStore<server::MarkedTypes<S>>) {
|
||||
s.$ity.alloc(self).encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for $ity {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
$ity {
|
||||
handle: handle::Handle::decode(r, s),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
define_handles! {
|
||||
'owned:
|
||||
FreeFunctions,
|
||||
TokenStream,
|
||||
Group,
|
||||
Literal,
|
||||
SourceFile,
|
||||
MultiSpan,
|
||||
Diagnostic,
|
||||
|
||||
'interned:
|
||||
Punct,
|
||||
Ident,
|
||||
Span,
|
||||
}
|
||||
|
||||
// FIXME(eddyb) generate these impls by pattern-matching on the
|
||||
// names of methods - also could use the presence of `fn drop`
|
||||
// to distinguish between 'owned and 'interned, above.
|
||||
// Alternatively, special "modes" could be listed of types in with_api
|
||||
// instead of pattern matching on methods, here and in server decl.
|
||||
|
||||
impl Clone for TokenStream {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Group {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Literal {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Literal {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Literal")
|
||||
// format the kind without quotes, as in `kind: Float`
|
||||
.field("kind", &format_args!("{}", &self.debug_kind()))
|
||||
.field("symbol", &self.symbol())
|
||||
// format `Some("...")` on one line even in {:#?} mode
|
||||
.field("suffix", &format_args!("{:?}", &self.suffix()))
|
||||
.field("span", &self.span())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SourceFile {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Span {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.debug())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_client_side {
|
||||
($($name:ident {
|
||||
$(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)*;)*
|
||||
}),* $(,)?) => {
|
||||
$(impl $name {
|
||||
$(pub(crate) fn $method($($arg: $arg_ty),*) $(-> $ret_ty)* {
|
||||
Bridge::with(|bridge| {
|
||||
let mut buf = bridge.cached_buffer.take();
|
||||
|
||||
buf.clear();
|
||||
api_tags::Method::$name(api_tags::$name::$method).encode(&mut buf, &mut ());
|
||||
reverse_encode!(buf; $($arg),*);
|
||||
|
||||
buf = bridge.dispatch.call(buf);
|
||||
|
||||
let r = Result::<_, PanicMessage>::decode(&mut &buf[..], &mut ());
|
||||
|
||||
bridge.cached_buffer = buf;
|
||||
|
||||
r.unwrap_or_else(|e| panic::resume_unwind(e.into()))
|
||||
})
|
||||
})*
|
||||
})*
|
||||
}
|
||||
}
|
||||
with_api!(self, self, define_client_side);
|
||||
|
||||
enum BridgeState<'a> {
|
||||
/// No server is currently connected to this client.
|
||||
NotConnected,
|
||||
|
||||
/// A server is connected and available for requests.
|
||||
Connected(Bridge<'a>),
|
||||
|
||||
/// Access to the bridge is being exclusively acquired
|
||||
/// (e.g., during `BridgeState::with`).
|
||||
InUse,
|
||||
}
|
||||
|
||||
enum BridgeStateL {}
|
||||
|
||||
impl<'a> scoped_cell::ApplyL<'a> for BridgeStateL {
|
||||
type Out = BridgeState<'a>;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static BRIDGE_STATE: scoped_cell::ScopedCell<BridgeStateL> =
|
||||
scoped_cell::ScopedCell::new(BridgeState::NotConnected);
|
||||
}
|
||||
|
||||
impl BridgeState<'_> {
|
||||
/// Take exclusive control of the thread-local
|
||||
/// `BridgeState`, and pass it to `f`, mutably.
|
||||
/// The state will be restored after `f` exits, even
|
||||
/// by panic, including modifications made to it by `f`.
|
||||
///
|
||||
/// N.B., while `f` is running, the thread-local state
|
||||
/// is `BridgeState::InUse`.
|
||||
fn with<R>(f: impl FnOnce(&mut BridgeState<'_>) -> R) -> R {
|
||||
BRIDGE_STATE.with(|state| {
|
||||
state.replace(BridgeState::InUse, |mut state| {
|
||||
// FIXME(#52812) pass `f` directly to `replace` when `RefMutL` is gone
|
||||
f(&mut state)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Bridge<'_> {
|
||||
pub(crate) fn is_available() -> bool {
|
||||
BridgeState::with(|state| match state {
|
||||
BridgeState::Connected(_) | BridgeState::InUse => true,
|
||||
BridgeState::NotConnected => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn enter<R>(self, f: impl FnOnce() -> R) -> R {
|
||||
let force_show_panics = self.force_show_panics;
|
||||
// Hide the default panic output within `proc_macro` expansions.
|
||||
// NB. the server can't do this because it may use a different libstd.
|
||||
static HIDE_PANICS_DURING_EXPANSION: Once = Once::new();
|
||||
HIDE_PANICS_DURING_EXPANSION.call_once(|| {
|
||||
let prev = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let show = BridgeState::with(|state| match state {
|
||||
BridgeState::NotConnected => true,
|
||||
BridgeState::Connected(_) | BridgeState::InUse => force_show_panics,
|
||||
});
|
||||
if show {
|
||||
prev(info)
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
BRIDGE_STATE.with(|state| state.set(BridgeState::Connected(self), f))
|
||||
}
|
||||
|
||||
fn with<R>(f: impl FnOnce(&mut Bridge<'_>) -> R) -> R {
|
||||
BridgeState::with(|state| match state {
|
||||
BridgeState::NotConnected => {
|
||||
panic!("procedural macro API is used outside of a procedural macro");
|
||||
}
|
||||
BridgeState::InUse => {
|
||||
panic!("procedural macro API is used while it's already in use");
|
||||
}
|
||||
BridgeState::Connected(bridge) => f(bridge),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Note that the (phantom) `I` ("input") and `O` ("output") type parameters
|
||||
/// decorate the `Client<I, O>` with the RPC "interface" of the entry-point, but
|
||||
/// do not themselves participate in ABI, at all, only facilitate type-checking.
|
||||
///
|
||||
/// E.g. `Client<TokenStream, TokenStream>` is the common proc macro interface,
|
||||
/// used for `#[proc_macro] fn foo(input: TokenStream) -> TokenStream`,
|
||||
/// indicating that the RPC input and output will be serialized token streams,
|
||||
/// and forcing the use of APIs that take/return `S::TokenStream`, server-side.
|
||||
#[repr(C)]
|
||||
pub struct Client<I, O> {
|
||||
// FIXME(eddyb) use a reference to the `static COUNTERS`, instead of
|
||||
// a wrapper `fn` pointer, once `const fn` can reference `static`s.
|
||||
pub(super) get_handle_counters: extern "C" fn() -> &'static HandleCounters,
|
||||
|
||||
pub(super) run: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
|
||||
pub(super) _marker: PhantomData<fn(I) -> O>,
|
||||
}
|
||||
|
||||
impl<I, O> Copy for Client<I, O> {}
|
||||
impl<I, O> Clone for Client<I, O> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Client-side helper for handling client panics, entering the bridge,
|
||||
/// deserializing input and serializing output.
|
||||
// FIXME(eddyb) maybe replace `Bridge::enter` with this?
|
||||
fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>(
|
||||
mut bridge: Bridge<'_>,
|
||||
f: impl FnOnce(A) -> R,
|
||||
) -> Buffer {
|
||||
// The initial `cached_buffer` contains the input.
|
||||
let mut buf = bridge.cached_buffer.take();
|
||||
|
||||
panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
bridge.enter(|| {
|
||||
let reader = &mut &buf[..];
|
||||
let input = A::decode(reader, &mut ());
|
||||
|
||||
// Put the `cached_buffer` back in the `Bridge`, for requests.
|
||||
Bridge::with(|bridge| bridge.cached_buffer = buf.take());
|
||||
|
||||
let output = f(input);
|
||||
|
||||
// Take the `cached_buffer` back out, for the output value.
|
||||
buf = Bridge::with(|bridge| bridge.cached_buffer.take());
|
||||
|
||||
// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
|
||||
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
|
||||
// having handles outside the `bridge.enter(|| ...)` scope, and
|
||||
// to catch panics that could happen while encoding the success.
|
||||
//
|
||||
// Note that panics should be impossible beyond this point, but
|
||||
// this is defensively trying to avoid any accidental panicking
|
||||
// reaching the `extern "C"` (which should `abort` but might not
|
||||
// at the moment, so this is also potentially preventing UB).
|
||||
buf.clear();
|
||||
Ok::<_, ()>(output).encode(&mut buf, &mut ());
|
||||
})
|
||||
}))
|
||||
.map_err(PanicMessage::from)
|
||||
.unwrap_or_else(|e| {
|
||||
buf.clear();
|
||||
Err::<(), _>(e).encode(&mut buf, &mut ());
|
||||
});
|
||||
buf
|
||||
}
|
||||
|
||||
impl Client<super::super::TokenStream, super::super::TokenStream> {
|
||||
pub const fn expand1(
|
||||
f: impl Fn(super::super::TokenStream) -> super::super::TokenStream + Copy,
|
||||
) -> Self {
|
||||
Client {
|
||||
get_handle_counters: HandleCounters::get,
|
||||
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
|
||||
run_client(bridge, |input| f(super::super::TokenStream(input)).0)
|
||||
}),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<(super::super::TokenStream, super::super::TokenStream), super::super::TokenStream> {
|
||||
pub const fn expand2(
|
||||
f: impl Fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream
|
||||
+ Copy,
|
||||
) -> Self {
|
||||
Client {
|
||||
get_handle_counters: HandleCounters::get,
|
||||
run: super::selfless_reify::reify_to_extern_c_fn_hrt_bridge(move |bridge| {
|
||||
run_client(bridge, |(input, input2)| {
|
||||
f(super::super::TokenStream(input), super::super::TokenStream(input2)).0
|
||||
})
|
||||
}),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ProcMacro {
|
||||
CustomDerive {
|
||||
trait_name: &'static str,
|
||||
attributes: &'static [&'static str],
|
||||
client: Client<super::super::TokenStream, super::super::TokenStream>,
|
||||
},
|
||||
|
||||
Attr {
|
||||
name: &'static str,
|
||||
client: Client<
|
||||
(super::super::TokenStream, super::super::TokenStream),
|
||||
super::super::TokenStream,
|
||||
>,
|
||||
},
|
||||
|
||||
Bang {
|
||||
name: &'static str,
|
||||
client: Client<super::super::TokenStream, super::super::TokenStream>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ProcMacro {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ProcMacro::CustomDerive { trait_name, .. } => trait_name,
|
||||
ProcMacro::Attr { name, .. } => name,
|
||||
ProcMacro::Bang { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn custom_derive(
|
||||
trait_name: &'static str,
|
||||
attributes: &'static [&'static str],
|
||||
expand: impl Fn(super::super::TokenStream) -> super::super::TokenStream + Copy,
|
||||
) -> Self {
|
||||
ProcMacro::CustomDerive { trait_name, attributes, client: Client::expand1(expand) }
|
||||
}
|
||||
|
||||
pub const fn attr(
|
||||
name: &'static str,
|
||||
expand: impl Fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream
|
||||
+ Copy,
|
||||
) -> Self {
|
||||
ProcMacro::Attr { name, client: Client::expand2(expand) }
|
||||
}
|
||||
|
||||
pub const fn bang(
|
||||
name: &'static str,
|
||||
expand: impl Fn(super::super::TokenStream) -> super::super::TokenStream + Copy,
|
||||
) -> Self {
|
||||
ProcMacro::Bang { name, client: Client::expand1(expand) }
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
//! Closure type (equivalent to `&mut dyn FnMut(A) -> R`) that's `repr(C)`.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Closure<'a, A, R> {
|
||||
call: unsafe extern "C" fn(*mut Env, A) -> R,
|
||||
env: *mut Env,
|
||||
// Prevent Send and Sync impls. `!Send`/`!Sync` is the usual way of doing
|
||||
// this, but that requires unstable features. rust-analyzer uses this code
|
||||
// and avoids unstable features.
|
||||
//
|
||||
// The `'a` lifetime parameter represents the lifetime of `Env`.
|
||||
_marker: PhantomData<*mut &'a mut ()>,
|
||||
}
|
||||
|
||||
struct Env;
|
||||
|
||||
impl<'a, A, R, F: FnMut(A) -> R> From<&'a mut F> for Closure<'a, A, R> {
|
||||
fn from(f: &'a mut F) -> Self {
|
||||
unsafe extern "C" fn call<A, R, F: FnMut(A) -> R>(env: *mut Env, arg: A) -> R {
|
||||
(*(env as *mut _ as *mut F))(arg)
|
||||
}
|
||||
Closure { call: call::<A, R, F>, env: f as *mut _ as *mut Env, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A, R> Closure<'a, A, R> {
|
||||
pub fn call(&mut self, arg: A) -> R {
|
||||
unsafe { (self.call)(self.env, arg) }
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
//! Server-side handles and storage for per-handle data.
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::num::NonZeroU32;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
pub(super) type Handle = NonZeroU32;
|
||||
|
||||
/// A store that associates values of type `T` with numeric handles. A value can
|
||||
/// be looked up using its handle.
|
||||
pub(super) struct OwnedStore<T: 'static> {
|
||||
counter: &'static AtomicUsize,
|
||||
data: BTreeMap<Handle, T>,
|
||||
}
|
||||
|
||||
impl<T> OwnedStore<T> {
|
||||
pub(super) fn new(counter: &'static AtomicUsize) -> Self {
|
||||
// Ensure the handle counter isn't 0, which would panic later,
|
||||
// when `NonZeroU32::new` (aka `Handle::new`) is called in `alloc`.
|
||||
assert_ne!(counter.load(Ordering::SeqCst), 0);
|
||||
|
||||
OwnedStore { counter, data: BTreeMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OwnedStore<T> {
|
||||
pub(super) fn alloc(&mut self, x: T) -> Handle {
|
||||
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
|
||||
let handle = Handle::new(counter as u32).expect("`proc_macro` handle counter overflowed");
|
||||
assert!(self.data.insert(handle, x).is_none());
|
||||
handle
|
||||
}
|
||||
|
||||
pub(super) fn take(&mut self, h: Handle) -> T {
|
||||
self.data.remove(&h).expect("use-after-free in `proc_macro` handle")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Handle> for OwnedStore<T> {
|
||||
type Output = T;
|
||||
fn index(&self, h: Handle) -> &T {
|
||||
self.data.get(&h).expect("use-after-free in `proc_macro` handle")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Handle> for OwnedStore<T> {
|
||||
fn index_mut(&mut self, h: Handle) -> &mut T {
|
||||
self.data.get_mut(&h).expect("use-after-free in `proc_macro` handle")
|
||||
}
|
||||
}
|
||||
|
||||
// HACK(eddyb) deterministic `std::collections::hash_map::RandomState` replacement
|
||||
// that doesn't require adding any dependencies to `proc_macro` (like `rustc-hash`).
|
||||
#[derive(Clone)]
|
||||
struct NonRandomState;
|
||||
|
||||
impl BuildHasher for NonRandomState {
|
||||
type Hasher = std::collections::hash_map::DefaultHasher;
|
||||
#[inline]
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
Self::Hasher::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `OwnedStore`, but avoids storing any value more than once.
|
||||
pub(super) struct InternedStore<T: 'static> {
|
||||
owned: OwnedStore<T>,
|
||||
interner: HashMap<T, Handle, NonRandomState>,
|
||||
}
|
||||
|
||||
impl<T: Copy + Eq + Hash> InternedStore<T> {
|
||||
pub(super) fn new(counter: &'static AtomicUsize) -> Self {
|
||||
InternedStore {
|
||||
owned: OwnedStore::new(counter),
|
||||
interner: HashMap::with_hasher(NonRandomState),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn alloc(&mut self, x: T) -> Handle {
|
||||
let owned = &mut self.owned;
|
||||
*self.interner.entry(x).or_insert_with(|| owned.alloc(x))
|
||||
}
|
||||
|
||||
pub(super) fn copy(&mut self, h: Handle) -> T {
|
||||
self.owned[h]
|
||||
}
|
||||
}
|
@ -1,451 +0,0 @@
|
||||
//! Internal interface for communicating between a `proc_macro` client
|
||||
//! (a proc macro crate) and a `proc_macro` server (a compiler front-end).
|
||||
//!
|
||||
//! Serialization (with C ABI buffers) and unique integer handles are employed
|
||||
//! to allow safely interfacing between two copies of `proc_macro` built
|
||||
//! (from the same source) by different compilers with potentially mismatching
|
||||
//! Rust ABIs (e.g., stage0/bin/rustc vs stage1/bin/rustc during bootstrap).
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub use super::{Delimiter, Level, LineColumn, Spacing};
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::ops::Bound;
|
||||
use std::panic;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Once;
|
||||
use std::thread;
|
||||
|
||||
/// Higher-order macro describing the server RPC API, allowing automatic
|
||||
/// generation of type-safe Rust APIs, both client-side and server-side.
|
||||
///
|
||||
/// `with_api!(MySelf, my_self, my_macro)` expands to:
|
||||
/// ```rust,ignore (pseudo-code)
|
||||
/// my_macro! {
|
||||
/// // ...
|
||||
/// Literal {
|
||||
/// // ...
|
||||
/// fn character(ch: char) -> MySelf::Literal;
|
||||
/// // ...
|
||||
/// fn span(my_self: &MySelf::Literal) -> MySelf::Span;
|
||||
/// fn set_span(my_self: &mut MySelf::Literal, span: MySelf::Span);
|
||||
/// },
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The first two arguments serve to customize the arguments names
|
||||
/// and argument/return types, to enable several different usecases:
|
||||
///
|
||||
/// If `my_self` is just `self`, then each `fn` signature can be used
|
||||
/// as-is for a method. If it's anything else (`self_` in practice),
|
||||
/// then the signatures don't have a special `self` argument, and
|
||||
/// can, therefore, have a different one introduced.
|
||||
///
|
||||
/// If `MySelf` is just `Self`, then the types are only valid inside
|
||||
/// a trait or a trait impl, where the trait has associated types
|
||||
/// for each of the API types. If non-associated types are desired,
|
||||
/// a module name (`self` in practice) can be used instead of `Self`.
|
||||
macro_rules! with_api {
|
||||
($S:ident, $self:ident, $m:ident) => {
|
||||
$m! {
|
||||
FreeFunctions {
|
||||
fn drop($self: $S::FreeFunctions);
|
||||
fn track_env_var(var: &str, value: Option<&str>);
|
||||
fn track_path(path: &str);
|
||||
},
|
||||
TokenStream {
|
||||
fn drop($self: $S::TokenStream);
|
||||
fn clone($self: &$S::TokenStream) -> $S::TokenStream;
|
||||
fn is_empty($self: &$S::TokenStream) -> bool;
|
||||
fn expand_expr($self: &$S::TokenStream) -> Result<$S::TokenStream, ()>;
|
||||
fn from_str(src: &str) -> $S::TokenStream;
|
||||
fn to_string($self: &$S::TokenStream) -> String;
|
||||
fn from_token_tree(
|
||||
tree: TokenTree<$S::Group, $S::Punct, $S::Ident, $S::Literal>,
|
||||
) -> $S::TokenStream;
|
||||
fn concat_trees(
|
||||
base: Option<$S::TokenStream>,
|
||||
trees: Vec<TokenTree<$S::Group, $S::Punct, $S::Ident, $S::Literal>>,
|
||||
) -> $S::TokenStream;
|
||||
fn concat_streams(
|
||||
base: Option<$S::TokenStream>,
|
||||
streams: Vec<$S::TokenStream>,
|
||||
) -> $S::TokenStream;
|
||||
fn into_trees(
|
||||
$self: $S::TokenStream
|
||||
) -> Vec<TokenTree<$S::Group, $S::Punct, $S::Ident, $S::Literal>>;
|
||||
},
|
||||
Group {
|
||||
fn drop($self: $S::Group);
|
||||
fn clone($self: &$S::Group) -> $S::Group;
|
||||
fn new(delimiter: Delimiter, stream: Option<$S::TokenStream>) -> $S::Group;
|
||||
fn delimiter($self: &$S::Group) -> Delimiter;
|
||||
fn stream($self: &$S::Group) -> $S::TokenStream;
|
||||
fn span($self: &$S::Group) -> $S::Span;
|
||||
fn span_open($self: &$S::Group) -> $S::Span;
|
||||
fn span_close($self: &$S::Group) -> $S::Span;
|
||||
fn set_span($self: &mut $S::Group, span: $S::Span);
|
||||
},
|
||||
Punct {
|
||||
fn new(ch: char, spacing: Spacing) -> $S::Punct;
|
||||
fn as_char($self: $S::Punct) -> char;
|
||||
fn spacing($self: $S::Punct) -> Spacing;
|
||||
fn span($self: $S::Punct) -> $S::Span;
|
||||
fn with_span($self: $S::Punct, span: $S::Span) -> $S::Punct;
|
||||
},
|
||||
Ident {
|
||||
fn new(string: &str, span: $S::Span, is_raw: bool) -> $S::Ident;
|
||||
fn span($self: $S::Ident) -> $S::Span;
|
||||
fn with_span($self: $S::Ident, span: $S::Span) -> $S::Ident;
|
||||
},
|
||||
Literal {
|
||||
fn drop($self: $S::Literal);
|
||||
fn clone($self: &$S::Literal) -> $S::Literal;
|
||||
fn from_str(s: &str) -> Result<$S::Literal, ()>;
|
||||
fn to_string($self: &$S::Literal) -> String;
|
||||
fn debug_kind($self: &$S::Literal) -> String;
|
||||
fn symbol($self: &$S::Literal) -> String;
|
||||
fn suffix($self: &$S::Literal) -> Option<String>;
|
||||
fn integer(n: &str) -> $S::Literal;
|
||||
fn typed_integer(n: &str, kind: &str) -> $S::Literal;
|
||||
fn float(n: &str) -> $S::Literal;
|
||||
fn f32(n: &str) -> $S::Literal;
|
||||
fn f64(n: &str) -> $S::Literal;
|
||||
fn string(string: &str) -> $S::Literal;
|
||||
fn character(ch: char) -> $S::Literal;
|
||||
fn byte_string(bytes: &[u8]) -> $S::Literal;
|
||||
fn span($self: &$S::Literal) -> $S::Span;
|
||||
fn set_span($self: &mut $S::Literal, span: $S::Span);
|
||||
fn subspan(
|
||||
$self: &$S::Literal,
|
||||
start: Bound<usize>,
|
||||
end: Bound<usize>,
|
||||
) -> Option<$S::Span>;
|
||||
},
|
||||
SourceFile {
|
||||
fn drop($self: $S::SourceFile);
|
||||
fn clone($self: &$S::SourceFile) -> $S::SourceFile;
|
||||
fn eq($self: &$S::SourceFile, other: &$S::SourceFile) -> bool;
|
||||
fn path($self: &$S::SourceFile) -> String;
|
||||
fn is_real($self: &$S::SourceFile) -> bool;
|
||||
},
|
||||
MultiSpan {
|
||||
fn drop($self: $S::MultiSpan);
|
||||
fn new() -> $S::MultiSpan;
|
||||
fn push($self: &mut $S::MultiSpan, span: $S::Span);
|
||||
},
|
||||
Diagnostic {
|
||||
fn drop($self: $S::Diagnostic);
|
||||
fn new(level: Level, msg: &str, span: $S::MultiSpan) -> $S::Diagnostic;
|
||||
fn sub(
|
||||
$self: &mut $S::Diagnostic,
|
||||
level: Level,
|
||||
msg: &str,
|
||||
span: $S::MultiSpan,
|
||||
);
|
||||
fn emit($self: $S::Diagnostic);
|
||||
},
|
||||
Span {
|
||||
fn debug($self: $S::Span) -> String;
|
||||
fn def_site() -> $S::Span;
|
||||
fn call_site() -> $S::Span;
|
||||
fn mixed_site() -> $S::Span;
|
||||
fn source_file($self: $S::Span) -> $S::SourceFile;
|
||||
fn parent($self: $S::Span) -> Option<$S::Span>;
|
||||
fn source($self: $S::Span) -> $S::Span;
|
||||
fn start($self: $S::Span) -> LineColumn;
|
||||
fn end($self: $S::Span) -> LineColumn;
|
||||
fn before($self: $S::Span) -> $S::Span;
|
||||
fn after($self: $S::Span) -> $S::Span;
|
||||
fn join($self: $S::Span, other: $S::Span) -> Option<$S::Span>;
|
||||
fn resolved_at($self: $S::Span, at: $S::Span) -> $S::Span;
|
||||
fn source_text($self: $S::Span) -> Option<String>;
|
||||
fn save_span($self: $S::Span) -> usize;
|
||||
fn recover_proc_macro_span(id: usize) -> $S::Span;
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME(eddyb) this calls `encode` for each argument, but in reverse,
|
||||
// to match the ordering in `reverse_decode`.
|
||||
macro_rules! reverse_encode {
|
||||
($writer:ident;) => {};
|
||||
($writer:ident; $first:ident $(, $rest:ident)*) => {
|
||||
reverse_encode!($writer; $($rest),*);
|
||||
$first.encode(&mut $writer, &mut ());
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(eddyb) this calls `decode` for each argument, but in reverse,
|
||||
// to avoid borrow conflicts from borrows started by `&mut` arguments.
|
||||
macro_rules! reverse_decode {
|
||||
($reader:ident, $s:ident;) => {};
|
||||
($reader:ident, $s:ident; $first:ident: $first_ty:ty $(, $rest:ident: $rest_ty:ty)*) => {
|
||||
reverse_decode!($reader, $s; $($rest: $rest_ty),*);
|
||||
let $first = <$first_ty>::decode(&mut $reader, $s);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
mod buffer;
|
||||
#[forbid(unsafe_code)]
|
||||
pub mod client;
|
||||
#[allow(unsafe_code)]
|
||||
mod closure;
|
||||
#[forbid(unsafe_code)]
|
||||
mod handle;
|
||||
#[macro_use]
|
||||
#[forbid(unsafe_code)]
|
||||
mod rpc;
|
||||
#[allow(unsafe_code)]
|
||||
mod scoped_cell;
|
||||
#[allow(unsafe_code)]
|
||||
mod selfless_reify;
|
||||
#[forbid(unsafe_code)]
|
||||
pub mod server;
|
||||
|
||||
use buffer::Buffer;
|
||||
pub use rpc::PanicMessage;
|
||||
use rpc::{Decode, DecodeMut, Encode, Reader, Writer};
|
||||
|
||||
/// An active connection between a server and a client.
|
||||
/// The server creates the bridge (`Bridge::run_server` in `server.rs`),
|
||||
/// then passes it to the client through the function pointer in the `run`
|
||||
/// field of `client::Client`. The client holds its copy of the `Bridge`
|
||||
/// in TLS during its execution (`Bridge::{enter, with}` in `client.rs`).
|
||||
#[repr(C)]
|
||||
pub struct Bridge<'a> {
|
||||
/// Reusable buffer (only `clear`-ed, never shrunk), primarily
|
||||
/// used for making requests, but also for passing input to client.
|
||||
cached_buffer: Buffer,
|
||||
|
||||
/// Server-side function that the client uses to make requests.
|
||||
dispatch: closure::Closure<'a, Buffer, Buffer>,
|
||||
|
||||
/// If 'true', always invoke the default panic hook
|
||||
force_show_panics: bool,
|
||||
|
||||
// Prevent Send and Sync impls. `!Send`/`!Sync` is the usual way of doing
|
||||
// this, but that requires unstable features. rust-analyzer uses this code
|
||||
// and avoids unstable features.
|
||||
_marker: marker::PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
#[forbid(unsafe_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
mod api_tags {
|
||||
use super::rpc::{DecodeMut, Encode, Reader, Writer};
|
||||
|
||||
macro_rules! declare_tags {
|
||||
($($name:ident {
|
||||
$(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)*;)*
|
||||
}),* $(,)?) => {
|
||||
$(
|
||||
pub(super) enum $name {
|
||||
$($method),*
|
||||
}
|
||||
rpc_encode_decode!(enum $name { $($method),* });
|
||||
)*
|
||||
|
||||
pub(super) enum Method {
|
||||
$($name($name)),*
|
||||
}
|
||||
rpc_encode_decode!(enum Method { $($name(m)),* });
|
||||
}
|
||||
}
|
||||
with_api!(self, self, declare_tags);
|
||||
}
|
||||
|
||||
/// Helper to wrap associated types to allow trait impl dispatch.
|
||||
/// That is, normally a pair of impls for `T::Foo` and `T::Bar`
|
||||
/// can overlap, but if the impls are, instead, on types like
|
||||
/// `Marked<T::Foo, Foo>` and `Marked<T::Bar, Bar>`, they can't.
|
||||
trait Mark {
|
||||
type Unmarked;
|
||||
fn mark(unmarked: Self::Unmarked) -> Self;
|
||||
}
|
||||
|
||||
/// Unwrap types wrapped by `Mark::mark` (see `Mark` for details).
|
||||
trait Unmark {
|
||||
type Unmarked;
|
||||
fn unmark(self) -> Self::Unmarked;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct Marked<T, M> {
|
||||
value: T,
|
||||
_marker: marker::PhantomData<M>,
|
||||
}
|
||||
|
||||
impl<T, M> Mark for Marked<T, M> {
|
||||
type Unmarked = T;
|
||||
fn mark(unmarked: Self::Unmarked) -> Self {
|
||||
Marked { value: unmarked, _marker: marker::PhantomData }
|
||||
}
|
||||
}
|
||||
impl<T, M> Unmark for Marked<T, M> {
|
||||
type Unmarked = T;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
impl<'a, T, M> Unmark for &'a Marked<T, M> {
|
||||
type Unmarked = &'a T;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<'a, T, M> Unmark for &'a mut Marked<T, M> {
|
||||
type Unmarked = &'a mut T;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Mark> Mark for Vec<T> {
|
||||
type Unmarked = Vec<T::Unmarked>;
|
||||
fn mark(unmarked: Self::Unmarked) -> Self {
|
||||
// Should be a no-op due to std's in-place collect optimizations.
|
||||
unmarked.into_iter().map(T::mark).collect()
|
||||
}
|
||||
}
|
||||
impl<T: Unmark> Unmark for Vec<T> {
|
||||
type Unmarked = Vec<T::Unmarked>;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
// Should be a no-op due to std's in-place collect optimizations.
|
||||
self.into_iter().map(T::unmark).collect()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mark_noop {
|
||||
($($ty:ty),* $(,)?) => {
|
||||
$(
|
||||
impl Mark for $ty {
|
||||
type Unmarked = Self;
|
||||
fn mark(unmarked: Self::Unmarked) -> Self {
|
||||
unmarked
|
||||
}
|
||||
}
|
||||
impl Unmark for $ty {
|
||||
type Unmarked = Self;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
self
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
mark_noop! {
|
||||
(),
|
||||
bool,
|
||||
char,
|
||||
&'_ [u8],
|
||||
&'_ str,
|
||||
String,
|
||||
usize,
|
||||
Delimiter,
|
||||
Level,
|
||||
LineColumn,
|
||||
Spacing,
|
||||
}
|
||||
|
||||
rpc_encode_decode!(
|
||||
enum Delimiter {
|
||||
Parenthesis,
|
||||
Brace,
|
||||
Bracket,
|
||||
None,
|
||||
}
|
||||
);
|
||||
rpc_encode_decode!(
|
||||
enum Level {
|
||||
Error,
|
||||
Warning,
|
||||
Note,
|
||||
Help,
|
||||
}
|
||||
);
|
||||
rpc_encode_decode!(struct LineColumn { line, column });
|
||||
rpc_encode_decode!(
|
||||
enum Spacing {
|
||||
Alone,
|
||||
Joint,
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! mark_compound {
|
||||
(enum $name:ident <$($T:ident),+> { $($variant:ident $(($field:ident))?),* $(,)? }) => {
|
||||
impl<$($T: Mark),+> Mark for $name <$($T),+> {
|
||||
type Unmarked = $name <$($T::Unmarked),+>;
|
||||
fn mark(unmarked: Self::Unmarked) -> Self {
|
||||
match unmarked {
|
||||
$($name::$variant $(($field))? => {
|
||||
$name::$variant $((Mark::mark($field)))?
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($T: Unmark),+> Unmark for $name <$($T),+> {
|
||||
type Unmarked = $name <$($T::Unmarked),+>;
|
||||
fn unmark(self) -> Self::Unmarked {
|
||||
match self {
|
||||
$($name::$variant $(($field))? => {
|
||||
$name::$variant $((Unmark::unmark($field)))?
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! compound_traits {
|
||||
($($t:tt)*) => {
|
||||
rpc_encode_decode!($($t)*);
|
||||
mark_compound!($($t)*);
|
||||
};
|
||||
}
|
||||
|
||||
compound_traits!(
|
||||
enum Bound<T> {
|
||||
Included(x),
|
||||
Excluded(x),
|
||||
Unbounded,
|
||||
}
|
||||
);
|
||||
|
||||
compound_traits!(
|
||||
enum Option<T> {
|
||||
Some(t),
|
||||
None,
|
||||
}
|
||||
);
|
||||
|
||||
compound_traits!(
|
||||
enum Result<T, E> {
|
||||
Ok(t),
|
||||
Err(e),
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TokenTree<G, P, I, L> {
|
||||
Group(G),
|
||||
Punct(P),
|
||||
Ident(I),
|
||||
Literal(L),
|
||||
}
|
||||
|
||||
compound_traits!(
|
||||
enum TokenTree<G, P, I, L> {
|
||||
Group(tt),
|
||||
Punct(tt),
|
||||
Ident(tt),
|
||||
Literal(tt),
|
||||
}
|
||||
);
|
@ -1,304 +0,0 @@
|
||||
//! Serialization for client-server communication.
|
||||
|
||||
use std::any::Any;
|
||||
use std::char;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroU32;
|
||||
use std::str;
|
||||
|
||||
pub(super) type Writer = super::buffer::Buffer;
|
||||
|
||||
pub(super) trait Encode<S>: Sized {
|
||||
fn encode(self, w: &mut Writer, s: &mut S);
|
||||
}
|
||||
|
||||
pub(super) type Reader<'a> = &'a [u8];
|
||||
|
||||
pub(super) trait Decode<'a, 's, S>: Sized {
|
||||
fn decode(r: &mut Reader<'a>, s: &'s S) -> Self;
|
||||
}
|
||||
|
||||
pub(super) trait DecodeMut<'a, 's, S>: Sized {
|
||||
fn decode(r: &mut Reader<'a>, s: &'s mut S) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! rpc_encode_decode {
|
||||
(le $ty:ty) => {
|
||||
impl<S> Encode<S> for $ty {
|
||||
fn encode(self, w: &mut Writer, _: &mut S) {
|
||||
w.extend_from_array(&self.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for $ty {
|
||||
fn decode(r: &mut Reader<'_>, _: &mut S) -> Self {
|
||||
const N: usize = ::std::mem::size_of::<$ty>();
|
||||
|
||||
let mut bytes = [0; N];
|
||||
bytes.copy_from_slice(&r[..N]);
|
||||
*r = &r[N..];
|
||||
|
||||
Self::from_le_bytes(bytes)
|
||||
}
|
||||
}
|
||||
};
|
||||
(struct $name:ident $(<$($T:ident),+>)? { $($field:ident),* $(,)? }) => {
|
||||
impl<S, $($($T: Encode<S>),+)?> Encode<S> for $name $(<$($T),+>)? {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
$(self.$field.encode(w, s);)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, $($($T: for<'s> DecodeMut<'a, 's, S>),+)?> DecodeMut<'a, '_, S>
|
||||
for $name $(<$($T),+>)?
|
||||
{
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
$name {
|
||||
$($field: DecodeMut::decode(r, s)),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(enum $name:ident $(<$($T:ident),+>)? { $($variant:ident $(($field:ident))*),* $(,)? }) => {
|
||||
impl<S, $($($T: Encode<S>),+)?> Encode<S> for $name $(<$($T),+>)? {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
// HACK(eddyb): `Tag` enum duplicated between the
|
||||
// two impls as there's no other place to stash it.
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod tag {
|
||||
#[repr(u8)] enum Tag { $($variant),* }
|
||||
|
||||
$(pub const $variant: u8 = Tag::$variant as u8;)*
|
||||
}
|
||||
|
||||
match self {
|
||||
$($name::$variant $(($field))* => {
|
||||
tag::$variant.encode(w, s);
|
||||
$($field.encode(w, s);)*
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, $($($T: for<'s> DecodeMut<'a, 's, S>),+)?> DecodeMut<'a, '_, S>
|
||||
for $name $(<$($T),+>)?
|
||||
{
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
// HACK(eddyb): `Tag` enum duplicated between the
|
||||
// two impls as there's no other place to stash it.
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod tag {
|
||||
#[repr(u8)] enum Tag { $($variant),* }
|
||||
|
||||
$(pub const $variant: u8 = Tag::$variant as u8;)*
|
||||
}
|
||||
|
||||
match u8::decode(r, s) {
|
||||
$(tag::$variant => {
|
||||
$(let $field = DecodeMut::decode(r, s);)*
|
||||
$name::$variant $(($field))*
|
||||
})*
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for () {
|
||||
fn encode(self, _: &mut Writer, _: &mut S) {}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for () {
|
||||
fn decode(_: &mut Reader<'_>, _: &mut S) -> Self {}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for u8 {
|
||||
fn encode(self, w: &mut Writer, _: &mut S) {
|
||||
w.push(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for u8 {
|
||||
fn decode(r: &mut Reader<'_>, _: &mut S) -> Self {
|
||||
let x = r[0];
|
||||
*r = &r[1..];
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
rpc_encode_decode!(le u32);
|
||||
rpc_encode_decode!(le usize);
|
||||
|
||||
impl<S> Encode<S> for bool {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
(self as u8).encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for bool {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
match u8::decode(r, s) {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for char {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
(self as u32).encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for char {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
char::from_u32(u32::decode(r, s)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for NonZeroU32 {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.get().encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for NonZeroU32 {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
Self::new(u32::decode(r, s)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, A: Encode<S>, B: Encode<S>> Encode<S> for (A, B) {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.0.encode(w, s);
|
||||
self.1.encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, A: for<'s> DecodeMut<'a, 's, S>, B: for<'s> DecodeMut<'a, 's, S>> DecodeMut<'a, '_, S>
|
||||
for (A, B)
|
||||
{
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
(DecodeMut::decode(r, s), DecodeMut::decode(r, s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for &[u8] {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.len().encode(w, s);
|
||||
w.write_all(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> DecodeMut<'a, '_, S> for &'a [u8] {
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
let len = usize::decode(r, s);
|
||||
let xs = &r[..len];
|
||||
*r = &r[len..];
|
||||
xs
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for &str {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.as_bytes().encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> DecodeMut<'a, '_, S> for &'a str {
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
str::from_utf8(<&[u8]>::decode(r, s)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for String {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self[..].encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for String {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
<&str>::decode(r, s).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Encode<S>> Encode<S> for Vec<T> {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.len().encode(w, s);
|
||||
for x in self {
|
||||
x.encode(w, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, T: for<'s> DecodeMut<'a, 's, S>> DecodeMut<'a, '_, S> for Vec<T> {
|
||||
fn decode(r: &mut Reader<'a>, s: &mut S) -> Self {
|
||||
let len = usize::decode(r, s);
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vec.push(T::decode(r, s));
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified version of panic payloads, ignoring
|
||||
/// types other than `&'static str` and `String`.
|
||||
pub enum PanicMessage {
|
||||
StaticStr(&'static str),
|
||||
String(String),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<Box<dyn Any + Send>> for PanicMessage {
|
||||
fn from(payload: Box<dyn Any + Send + 'static>) -> Self {
|
||||
if let Some(s) = payload.downcast_ref::<&'static str>() {
|
||||
return PanicMessage::StaticStr(s);
|
||||
}
|
||||
if let Ok(s) = payload.downcast::<String>() {
|
||||
return PanicMessage::String(*s);
|
||||
}
|
||||
PanicMessage::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Box<dyn Any + Send>> for PanicMessage {
|
||||
fn into(self) -> Box<dyn Any + Send> {
|
||||
match self {
|
||||
PanicMessage::StaticStr(s) => Box::new(s),
|
||||
PanicMessage::String(s) => Box::new(s),
|
||||
PanicMessage::Unknown => {
|
||||
struct UnknownPanicMessage;
|
||||
Box::new(UnknownPanicMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PanicMessage {
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match self {
|
||||
PanicMessage::StaticStr(s) => Some(s),
|
||||
PanicMessage::String(s) => Some(s),
|
||||
PanicMessage::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Encode<S> for PanicMessage {
|
||||
fn encode(self, w: &mut Writer, s: &mut S) {
|
||||
self.as_str().encode(w, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DecodeMut<'_, '_, S> for PanicMessage {
|
||||
fn decode(r: &mut Reader<'_>, s: &mut S) -> Self {
|
||||
match Option::<String>::decode(r, s) {
|
||||
Some(s) => PanicMessage::String(s),
|
||||
None => PanicMessage::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
//! `Cell` variant for (scoped) existential lifetimes.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Type lambda application, with a lifetime.
|
||||
#[allow(unused_lifetimes)]
|
||||
pub trait ApplyL<'a> {
|
||||
type Out;
|
||||
}
|
||||
|
||||
/// Type lambda taking a lifetime, i.e., `Lifetime -> Type`.
|
||||
pub trait LambdaL: for<'a> ApplyL<'a> {}
|
||||
|
||||
impl<T: for<'a> ApplyL<'a>> LambdaL for T {}
|
||||
|
||||
// HACK(eddyb) work around projection limitations with a newtype
|
||||
// FIXME(#52812) replace with `&'a mut <T as ApplyL<'b>>::Out`
|
||||
pub struct RefMutL<'a, 'b, T: LambdaL>(&'a mut <T as ApplyL<'b>>::Out);
|
||||
|
||||
impl<'a, 'b, T: LambdaL> Deref for RefMutL<'a, 'b, T> {
|
||||
type Target = <T as ApplyL<'b>>::Out;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, T: LambdaL> DerefMut for RefMutL<'a, 'b, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScopedCell<T: LambdaL>(Cell<<T as ApplyL<'static>>::Out>);
|
||||
|
||||
impl<T: LambdaL> ScopedCell<T> {
|
||||
pub const fn new(value: <T as ApplyL<'static>>::Out) -> Self {
|
||||
ScopedCell(Cell::new(value))
|
||||
}
|
||||
|
||||
/// Sets the value in `self` to `replacement` while
|
||||
/// running `f`, which gets the old value, mutably.
|
||||
/// The old value will be restored after `f` exits, even
|
||||
/// by panic, including modifications made to it by `f`.
|
||||
pub fn replace<'a, R>(
|
||||
&self,
|
||||
replacement: <T as ApplyL<'a>>::Out,
|
||||
f: impl for<'b, 'c> FnOnce(RefMutL<'b, 'c, T>) -> R,
|
||||
) -> R {
|
||||
/// Wrapper that ensures that the cell always gets filled
|
||||
/// (with the original state, optionally changed by `f`),
|
||||
/// even if `f` had panicked.
|
||||
struct PutBackOnDrop<'a, T: LambdaL> {
|
||||
cell: &'a ScopedCell<T>,
|
||||
value: Option<<T as ApplyL<'static>>::Out>,
|
||||
}
|
||||
|
||||
impl<'a, T: LambdaL> Drop for PutBackOnDrop<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.cell.0.set(self.value.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let mut put_back_on_drop = PutBackOnDrop {
|
||||
cell: self,
|
||||
value: Some(self.0.replace(unsafe {
|
||||
let erased = mem::transmute_copy(&replacement);
|
||||
mem::forget(replacement);
|
||||
erased
|
||||
})),
|
||||
};
|
||||
|
||||
f(RefMutL(put_back_on_drop.value.as_mut().unwrap()))
|
||||
}
|
||||
|
||||
/// Sets the value in `self` to `value` while running `f`.
|
||||
pub fn set<R>(&self, value: <T as ApplyL<'_>>::Out, f: impl FnOnce() -> R) -> R {
|
||||
self.replace(value, |_| f())
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
//! Abstraction for creating `fn` pointers from any callable that *effectively*
|
||||
//! has the equivalent of implementing `Default`, even if the compiler neither
|
||||
//! provides `Default` nor allows reifying closures (i.e. creating `fn` pointers)
|
||||
//! other than those with absolutely no captures.
|
||||
//!
|
||||
//! More specifically, for a closure-like type to be "effectively `Default`":
|
||||
//! * it must be a ZST (zero-sized type): no information contained within, so
|
||||
//! that `Default`'s return value (if it were implemented) is unambiguous
|
||||
//! * it must be `Copy`: no captured "unique ZST tokens" or any other similar
|
||||
//! types that would make duplicating values at will unsound
|
||||
//! * combined with the ZST requirement, this confers a kind of "telecopy"
|
||||
//! ability: similar to `Copy`, but without keeping the value around, and
|
||||
//! instead "reconstructing" it (a noop given it's a ZST) when needed
|
||||
//! * it must be *provably* inhabited: no captured uninhabited types or any
|
||||
//! other types that cannot be constructed by the user of this abstraction
|
||||
//! * the proof is a value of the closure-like type itself, in a sense the
|
||||
//! "seed" for the "telecopy" process made possible by ZST + `Copy`
|
||||
//! * this requirement is the only reason an abstraction limited to a specific
|
||||
//! usecase is required: ZST + `Copy` can be checked with *at worst* a panic
|
||||
//! at the "attempted `::default()` call" time, but that doesn't guarantee
|
||||
//! that the value can be soundly created, and attempting to use the typical
|
||||
//! "proof ZST token" approach leads yet again to having a ZST + `Copy` type
|
||||
//! that is not proof of anything without a value (i.e. isomorphic to a
|
||||
//! newtype of the type it's trying to prove the inhabitation of)
|
||||
//!
|
||||
//! A more flexible (and safer) solution to the general problem could exist once
|
||||
//! `const`-generic parameters can have type parameters in their types:
|
||||
//!
|
||||
//! ```rust,ignore (needs future const-generics)
|
||||
//! extern "C" fn ffi_wrapper<
|
||||
//! A, R,
|
||||
//! F: Fn(A) -> R,
|
||||
//! const f: F, // <-- this `const`-generic is not yet allowed
|
||||
//! >(arg: A) -> R {
|
||||
//! f(arg)
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::mem;
|
||||
|
||||
// FIXME(eddyb) this could be `trait` impls except for the `const fn` requirement.
|
||||
macro_rules! define_reify_functions {
|
||||
($(
|
||||
fn $name:ident $(<$($param:ident),*>)?
|
||||
for $(extern $abi:tt)? fn($($arg:ident: $arg_ty:ty),*) -> $ret_ty:ty;
|
||||
)+) => {
|
||||
$(pub const fn $name<
|
||||
$($($param,)*)?
|
||||
F: Fn($($arg_ty),*) -> $ret_ty + Copy
|
||||
>(f: F) -> $(extern $abi)? fn($($arg_ty),*) -> $ret_ty {
|
||||
// FIXME(eddyb) describe the `F` type (e.g. via `type_name::<F>`) once panic
|
||||
// formatting becomes possible in `const fn`.
|
||||
assert!(mem::size_of::<F>() == 0, "selfless_reify: closure must be zero-sized");
|
||||
|
||||
$(extern $abi)? fn wrapper<
|
||||
$($($param,)*)?
|
||||
F: Fn($($arg_ty),*) -> $ret_ty + Copy
|
||||
>($($arg: $arg_ty),*) -> $ret_ty {
|
||||
let f = unsafe {
|
||||
// SAFETY: `F` satisfies all criteria for "out of thin air"
|
||||
// reconstructability (see module-level doc comment).
|
||||
mem::MaybeUninit::<F>::uninit().assume_init()
|
||||
};
|
||||
f($($arg),*)
|
||||
}
|
||||
let _f_proof = f;
|
||||
wrapper::<
|
||||
$($($param,)*)?
|
||||
F
|
||||
>
|
||||
})+
|
||||
}
|
||||
}
|
||||
|
||||
define_reify_functions! {
|
||||
fn _reify_to_extern_c_fn_unary<A, R> for extern "C" fn(arg: A) -> R;
|
||||
|
||||
// HACK(eddyb) this abstraction is used with `for<'a> fn(Bridge<'a>) -> T`
|
||||
// but that doesn't work with just `reify_to_extern_c_fn_unary` because of
|
||||
// the `fn` pointer type being "higher-ranked" (i.e. the `for<'a>` binder).
|
||||
// FIXME(eddyb) try to remove the lifetime from `Bridge`, that'd help.
|
||||
fn reify_to_extern_c_fn_hrt_bridge<R> for extern "C" fn(bridge: super::Bridge<'_>) -> R;
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
//! Server-side traits.
|
||||
|
||||
use super::*;
|
||||
|
||||
// FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`.
|
||||
use super::client::HandleStore;
|
||||
|
||||
pub trait Types {
|
||||
type FreeFunctions: 'static;
|
||||
type TokenStream: 'static + Clone;
|
||||
type Group: 'static + Clone;
|
||||
type Punct: 'static + Copy + Eq + Hash;
|
||||
type Ident: 'static + Copy + Eq + Hash;
|
||||
type Literal: 'static + Clone;
|
||||
type SourceFile: 'static + Clone;
|
||||
type MultiSpan: 'static;
|
||||
type Diagnostic: 'static;
|
||||
type Span: 'static + Copy + Eq + Hash;
|
||||
}
|
||||
|
||||
/// Declare an associated fn of one of the traits below, adding necessary
|
||||
/// default bodies.
|
||||
macro_rules! associated_fn {
|
||||
(fn drop(&mut self, $arg:ident: $arg_ty:ty)) =>
|
||||
(fn drop(&mut self, $arg: $arg_ty) { mem::drop($arg) });
|
||||
|
||||
(fn clone(&mut self, $arg:ident: $arg_ty:ty) -> $ret_ty:ty) =>
|
||||
(fn clone(&mut self, $arg: $arg_ty) -> $ret_ty { $arg.clone() });
|
||||
|
||||
($($item:tt)*) => ($($item)*;)
|
||||
}
|
||||
|
||||
macro_rules! declare_server_traits {
|
||||
($($name:ident {
|
||||
$(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)*
|
||||
}),* $(,)?) => {
|
||||
$(pub trait $name: Types {
|
||||
$(associated_fn!(fn $method(&mut self, $($arg: $arg_ty),*) $(-> $ret_ty)?);)*
|
||||
})*
|
||||
|
||||
pub trait Server: Types $(+ $name)* {}
|
||||
impl<S: Types $(+ $name)*> Server for S {}
|
||||
}
|
||||
}
|
||||
with_api!(Self, self_, declare_server_traits);
|
||||
|
||||
pub(super) struct MarkedTypes<S: Types>(S);
|
||||
|
||||
macro_rules! define_mark_types_impls {
|
||||
($($name:ident {
|
||||
$(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)*
|
||||
}),* $(,)?) => {
|
||||
impl<S: Types> Types for MarkedTypes<S> {
|
||||
$(type $name = Marked<S::$name, client::$name>;)*
|
||||
}
|
||||
|
||||
$(impl<S: $name> $name for MarkedTypes<S> {
|
||||
$(fn $method(&mut self, $($arg: $arg_ty),*) $(-> $ret_ty)? {
|
||||
<_>::mark($name::$method(&mut self.0, $($arg.unmark()),*))
|
||||
})*
|
||||
})*
|
||||
}
|
||||
}
|
||||
with_api!(Self, self_, define_mark_types_impls);
|
||||
|
||||
struct Dispatcher<S: Types> {
|
||||
handle_store: HandleStore<S>,
|
||||
server: S,
|
||||
}
|
||||
|
||||
macro_rules! define_dispatcher_impl {
|
||||
($($name:ident {
|
||||
$(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)*
|
||||
}),* $(,)?) => {
|
||||
// FIXME(eddyb) `pub` only for `ExecutionStrategy` below.
|
||||
pub trait DispatcherTrait {
|
||||
// HACK(eddyb) these are here to allow `Self::$name` to work below.
|
||||
$(type $name;)*
|
||||
fn dispatch(&mut self, buf: Buffer) -> Buffer;
|
||||
}
|
||||
|
||||
impl<S: Server> DispatcherTrait for Dispatcher<MarkedTypes<S>> {
|
||||
$(type $name = <MarkedTypes<S> as Types>::$name;)*
|
||||
fn dispatch(&mut self, mut buf: Buffer) -> Buffer {
|
||||
let Dispatcher { handle_store, server } = self;
|
||||
|
||||
let mut reader = &buf[..];
|
||||
match api_tags::Method::decode(&mut reader, &mut ()) {
|
||||
$(api_tags::Method::$name(m) => match m {
|
||||
$(api_tags::$name::$method => {
|
||||
let mut call_method = || {
|
||||
reverse_decode!(reader, handle_store; $($arg: $arg_ty),*);
|
||||
$name::$method(server, $($arg),*)
|
||||
};
|
||||
// HACK(eddyb) don't use `panic::catch_unwind` in a panic.
|
||||
// If client and server happen to use the same `libstd`,
|
||||
// `catch_unwind` asserts that the panic counter was 0,
|
||||
// even when the closure passed to it didn't panic.
|
||||
let r = if thread::panicking() {
|
||||
Ok(call_method())
|
||||
} else {
|
||||
panic::catch_unwind(panic::AssertUnwindSafe(call_method))
|
||||
.map_err(PanicMessage::from)
|
||||
};
|
||||
|
||||
buf.clear();
|
||||
r.encode(&mut buf, handle_store);
|
||||
})*
|
||||
}),*
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
with_api!(Self, self_, define_dispatcher_impl);
|
||||
|
||||
pub trait ExecutionStrategy {
|
||||
fn run_bridge_and_client(
|
||||
&self,
|
||||
dispatcher: &mut impl DispatcherTrait,
|
||||
input: Buffer,
|
||||
run_client: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
force_show_panics: bool,
|
||||
) -> Buffer;
|
||||
}
|
||||
|
||||
pub struct SameThread;
|
||||
|
||||
impl ExecutionStrategy for SameThread {
|
||||
fn run_bridge_and_client(
|
||||
&self,
|
||||
dispatcher: &mut impl DispatcherTrait,
|
||||
input: Buffer,
|
||||
run_client: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
force_show_panics: bool,
|
||||
) -> Buffer {
|
||||
let mut dispatch = |buf| dispatcher.dispatch(buf);
|
||||
|
||||
run_client(Bridge {
|
||||
cached_buffer: input,
|
||||
dispatch: (&mut dispatch).into(),
|
||||
force_show_panics,
|
||||
_marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(eddyb) Two implementations are provided, the second one is a bit
|
||||
// faster but neither is anywhere near as fast as same-thread execution.
|
||||
|
||||
pub struct CrossThread1;
|
||||
|
||||
impl ExecutionStrategy for CrossThread1 {
|
||||
fn run_bridge_and_client(
|
||||
&self,
|
||||
dispatcher: &mut impl DispatcherTrait,
|
||||
input: Buffer,
|
||||
run_client: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
force_show_panics: bool,
|
||||
) -> Buffer {
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
let (req_tx, req_rx) = channel();
|
||||
let (res_tx, res_rx) = channel();
|
||||
|
||||
let join_handle = thread::spawn(move || {
|
||||
let mut dispatch = |buf| {
|
||||
req_tx.send(buf).unwrap();
|
||||
res_rx.recv().unwrap()
|
||||
};
|
||||
|
||||
run_client(Bridge {
|
||||
cached_buffer: input,
|
||||
dispatch: (&mut dispatch).into(),
|
||||
force_show_panics,
|
||||
_marker: marker::PhantomData,
|
||||
})
|
||||
});
|
||||
|
||||
for b in req_rx {
|
||||
res_tx.send(dispatcher.dispatch(b)).unwrap();
|
||||
}
|
||||
|
||||
join_handle.join().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CrossThread2;
|
||||
|
||||
impl ExecutionStrategy for CrossThread2 {
|
||||
fn run_bridge_and_client(
|
||||
&self,
|
||||
dispatcher: &mut impl DispatcherTrait,
|
||||
input: Buffer,
|
||||
run_client: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
force_show_panics: bool,
|
||||
) -> Buffer {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
enum State<T> {
|
||||
Req(T),
|
||||
Res(T),
|
||||
}
|
||||
|
||||
let mut state = Arc::new(Mutex::new(State::Res(Buffer::new())));
|
||||
|
||||
let server_thread = thread::current();
|
||||
let state2 = state.clone();
|
||||
let join_handle = thread::spawn(move || {
|
||||
let mut dispatch = |b| {
|
||||
*state2.lock().unwrap() = State::Req(b);
|
||||
server_thread.unpark();
|
||||
loop {
|
||||
thread::park();
|
||||
if let State::Res(b) = &mut *state2.lock().unwrap() {
|
||||
break b.take();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let r = run_client(Bridge {
|
||||
cached_buffer: input,
|
||||
dispatch: (&mut dispatch).into(),
|
||||
force_show_panics,
|
||||
_marker: marker::PhantomData,
|
||||
});
|
||||
|
||||
// Wake up the server so it can exit the dispatch loop.
|
||||
drop(state2);
|
||||
server_thread.unpark();
|
||||
|
||||
r
|
||||
});
|
||||
|
||||
// Check whether `state2` was dropped, to know when to stop.
|
||||
while Arc::get_mut(&mut state).is_none() {
|
||||
thread::park();
|
||||
let mut b = match &mut *state.lock().unwrap() {
|
||||
State::Req(b) => b.take(),
|
||||
_ => continue,
|
||||
};
|
||||
b = dispatcher.dispatch(b.take());
|
||||
*state.lock().unwrap() = State::Res(b);
|
||||
join_handle.thread().unpark();
|
||||
}
|
||||
|
||||
join_handle.join().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_server<
|
||||
S: Server,
|
||||
I: Encode<HandleStore<MarkedTypes<S>>>,
|
||||
O: for<'a, 's> DecodeMut<'a, 's, HandleStore<MarkedTypes<S>>>,
|
||||
>(
|
||||
strategy: &impl ExecutionStrategy,
|
||||
handle_counters: &'static client::HandleCounters,
|
||||
server: S,
|
||||
input: I,
|
||||
run_client: extern "C" fn(Bridge<'_>) -> Buffer,
|
||||
force_show_panics: bool,
|
||||
) -> Result<O, PanicMessage> {
|
||||
let mut dispatcher =
|
||||
Dispatcher { handle_store: HandleStore::new(handle_counters), server: MarkedTypes(server) };
|
||||
|
||||
let mut buf = Buffer::new();
|
||||
input.encode(&mut buf, &mut dispatcher.handle_store);
|
||||
|
||||
buf = strategy.run_bridge_and_client(&mut dispatcher, buf, run_client, force_show_panics);
|
||||
|
||||
Result::decode(&mut &buf[..], &mut dispatcher.handle_store)
|
||||
}
|
||||
|
||||
impl client::Client<super::super::TokenStream, super::super::TokenStream> {
|
||||
pub fn run<S>(
|
||||
&self,
|
||||
strategy: &impl ExecutionStrategy,
|
||||
server: S,
|
||||
input: S::TokenStream,
|
||||
force_show_panics: bool,
|
||||
) -> Result<S::TokenStream, PanicMessage>
|
||||
where
|
||||
S: Server,
|
||||
S::TokenStream: Default,
|
||||
{
|
||||
let client::Client { get_handle_counters, run, _marker } = *self;
|
||||
run_server(
|
||||
strategy,
|
||||
get_handle_counters(),
|
||||
server,
|
||||
<MarkedTypes<S> as Types>::TokenStream::mark(input),
|
||||
run,
|
||||
force_show_panics,
|
||||
)
|
||||
.map(|s| <Option<<MarkedTypes<S> as Types>::TokenStream>>::unmark(s).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
client::Client<
|
||||
(super::super::TokenStream, super::super::TokenStream),
|
||||
super::super::TokenStream,
|
||||
>
|
||||
{
|
||||
pub fn run<S>(
|
||||
&self,
|
||||
strategy: &impl ExecutionStrategy,
|
||||
server: S,
|
||||
input: S::TokenStream,
|
||||
input2: S::TokenStream,
|
||||
force_show_panics: bool,
|
||||
) -> Result<S::TokenStream, PanicMessage>
|
||||
where
|
||||
S: Server,
|
||||
S::TokenStream: Default,
|
||||
{
|
||||
let client::Client { get_handle_counters, run, _marker } = *self;
|
||||
run_server(
|
||||
strategy,
|
||||
get_handle_counters(),
|
||||
server,
|
||||
(
|
||||
<MarkedTypes<S> as Types>::TokenStream::mark(input),
|
||||
<MarkedTypes<S> as Types>::TokenStream::mark(input2),
|
||||
),
|
||||
run,
|
||||
force_show_panics,
|
||||
)
|
||||
.map(|s| <Option<<MarkedTypes<S> as Types>::TokenStream>>::unmark(s).unwrap_or_default())
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
//! lib-proc-macro diagnostic
|
||||
//!
|
||||
//! Copy from <https://github.com/rust-lang/rust/blob/e45d9973b2665897a768312e971b82cc62633103/src/libproc_macro/diagnostic.rs>
|
||||
//! augmented with removing unstable features
|
||||
|
||||
use super::Span;
|
||||
|
||||
/// An enum representing a diagnostic level.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Level {
|
||||
/// An error.
|
||||
Error,
|
||||
/// A warning.
|
||||
Warning,
|
||||
/// A note.
|
||||
Note,
|
||||
/// A help message.
|
||||
Help,
|
||||
}
|
||||
|
||||
/// Trait implemented by types that can be converted into a set of `Span`s.
|
||||
pub trait MultiSpan {
|
||||
/// Converts `self` into a `Vec<Span>`.
|
||||
fn into_spans(self) -> Vec<Span>;
|
||||
}
|
||||
|
||||
impl MultiSpan for Span {
|
||||
fn into_spans(self) -> Vec<Span> {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiSpan for Vec<Span> {
|
||||
fn into_spans(self) -> Vec<Span> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MultiSpan for &'a [Span] {
|
||||
fn into_spans(self) -> Vec<Span> {
|
||||
self.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a diagnostic message and associated children
|
||||
/// messages.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Diagnostic {
|
||||
level: Level,
|
||||
message: String,
|
||||
spans: Vec<Span>,
|
||||
children: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
macro_rules! diagnostic_child_methods {
|
||||
($spanned:ident, $regular:ident, $level:expr) => {
|
||||
#[doc = concat!("Adds a new child diagnostics message to `self` with the [`",
|
||||
stringify!($level), "`] level, and the given `spans` and `message`.")]
|
||||
pub fn $spanned<S, T>(mut self, spans: S, message: T) -> Diagnostic
|
||||
where
|
||||
S: MultiSpan,
|
||||
T: Into<String>,
|
||||
{
|
||||
self.children.push(Diagnostic::spanned(spans, $level, message));
|
||||
self
|
||||
}
|
||||
|
||||
#[doc = concat!("Adds a new child diagnostic message to `self` with the [`",
|
||||
stringify!($level), "`] level, and the given `message`.")]
|
||||
pub fn $regular<T: Into<String>>(mut self, message: T) -> Diagnostic {
|
||||
self.children.push(Diagnostic::new($level, message));
|
||||
self
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Iterator over the children diagnostics of a `Diagnostic`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Children<'a>(std::slice::Iter<'a, Diagnostic>);
|
||||
|
||||
impl<'a> Iterator for Children<'a> {
|
||||
type Item = &'a Diagnostic;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Creates a new diagnostic with the given `level` and `message`.
|
||||
pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
|
||||
Diagnostic { level, message: message.into(), spans: vec![], children: vec![] }
|
||||
}
|
||||
|
||||
/// Creates a new diagnostic with the given `level` and `message` pointing to
|
||||
/// the given set of `spans`.
|
||||
pub fn spanned<S, T>(spans: S, level: Level, message: T) -> Diagnostic
|
||||
where
|
||||
S: MultiSpan,
|
||||
T: Into<String>,
|
||||
{
|
||||
Diagnostic { level, message: message.into(), spans: spans.into_spans(), children: vec![] }
|
||||
}
|
||||
|
||||
diagnostic_child_methods!(span_error, error, Level::Error);
|
||||
diagnostic_child_methods!(span_warning, warning, Level::Warning);
|
||||
diagnostic_child_methods!(span_note, note, Level::Note);
|
||||
diagnostic_child_methods!(span_help, help, Level::Help);
|
||||
|
||||
/// Returns the diagnostic `level` for `self`.
|
||||
pub fn level(&self) -> Level {
|
||||
self.level
|
||||
}
|
||||
|
||||
/// Sets the level in `self` to `level`.
|
||||
pub fn set_level(&mut self, level: Level) {
|
||||
self.level = level;
|
||||
}
|
||||
|
||||
/// Returns the message in `self`.
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Sets the message in `self` to `message`.
|
||||
pub fn set_message<T: Into<String>>(&mut self, message: T) {
|
||||
self.message = message.into();
|
||||
}
|
||||
|
||||
/// Returns the `Span`s in `self`.
|
||||
pub fn spans(&self) -> &[Span] {
|
||||
&self.spans
|
||||
}
|
||||
|
||||
/// Sets the `Span`s in `self` to `spans`.
|
||||
pub fn set_spans<S: MultiSpan>(&mut self, spans: S) {
|
||||
self.spans = spans.into_spans();
|
||||
}
|
||||
|
||||
/// Returns an iterator over the children diagnostics of `self`.
|
||||
pub fn children(&self) -> Children<'_> {
|
||||
Children(self.children.iter())
|
||||
}
|
||||
|
||||
/// Emit the diagnostic.
|
||||
pub fn emit(self) {
|
||||
fn to_internal(spans: Vec<Span>) -> super::bridge::client::MultiSpan {
|
||||
let mut multi_span = super::bridge::client::MultiSpan::new();
|
||||
for span in spans {
|
||||
multi_span.push(span.0);
|
||||
}
|
||||
multi_span
|
||||
}
|
||||
|
||||
let mut diag = super::bridge::client::Diagnostic::new(
|
||||
self.level,
|
||||
&self.message[..],
|
||||
to_internal(self.spans),
|
||||
);
|
||||
for c in self.children {
|
||||
diag.sub(c.level, &c.message[..], to_internal(c.spans));
|
||||
}
|
||||
diag.emit();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,139 +0,0 @@
|
||||
//! # Quasiquoter
|
||||
//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
|
||||
|
||||
//! This quasiquoter uses macros 2.0 hygiene to reliably access
|
||||
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
|
||||
|
||||
use super::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
|
||||
|
||||
macro_rules! quote_tt {
|
||||
(($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) };
|
||||
([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) };
|
||||
({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) };
|
||||
(,) => { Punct::new(',', Spacing::Alone) };
|
||||
(.) => { Punct::new('.', Spacing::Alone) };
|
||||
(;) => { Punct::new(';', Spacing::Alone) };
|
||||
(!) => { Punct::new('!', Spacing::Alone) };
|
||||
(<) => { Punct::new('<', Spacing::Alone) };
|
||||
(>) => { Punct::new('>', Spacing::Alone) };
|
||||
(&) => { Punct::new('&', Spacing::Alone) };
|
||||
(=) => { Punct::new('=', Spacing::Alone) };
|
||||
($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
|
||||
}
|
||||
|
||||
macro_rules! quote_ts {
|
||||
((@ $($t:tt)*)) => { $($t)* };
|
||||
(::) => {
|
||||
[
|
||||
TokenTree::from(Punct::new(':', Spacing::Joint)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Alone)),
|
||||
].iter()
|
||||
.cloned()
|
||||
.map(|mut x| {
|
||||
x.set_span(Span::def_site());
|
||||
x
|
||||
})
|
||||
.collect::<TokenStream>()
|
||||
};
|
||||
($t:tt) => { TokenTree::from(quote_tt!($t)) };
|
||||
}
|
||||
|
||||
/// Simpler version of the real `quote!` macro, implemented solely
|
||||
/// through `macro_rules`, for bootstrapping the real implementation
|
||||
/// (see the `quote` function), which does not have access to the
|
||||
/// real `quote!` macro due to the `proc_macro` crate not being
|
||||
/// able to depend on itself.
|
||||
///
|
||||
/// Note: supported tokens are a subset of the real `quote!`, but
|
||||
/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
|
||||
macro_rules! quote {
|
||||
() => { TokenStream::new() };
|
||||
($($t:tt)*) => {
|
||||
[
|
||||
$(TokenStream::from(quote_ts!($t)),)*
|
||||
].iter().cloned().collect::<TokenStream>()
|
||||
};
|
||||
}
|
||||
|
||||
/// Quote a `TokenStream` into a `TokenStream`.
|
||||
/// This is the actual implementation of the `quote!()` proc macro.
|
||||
///
|
||||
/// It is loaded by the compiler in `register_builtin_macros`.
|
||||
pub fn quote(stream: TokenStream) -> TokenStream {
|
||||
if stream.is_empty() {
|
||||
return quote!(super::TokenStream::new());
|
||||
}
|
||||
let proc_macro_crate = quote!(crate);
|
||||
let mut after_dollar = false;
|
||||
let tokens = stream
|
||||
.into_iter()
|
||||
.filter_map(|tree| {
|
||||
if after_dollar {
|
||||
after_dollar = false;
|
||||
match tree {
|
||||
TokenTree::Ident(_) => {
|
||||
return Some(quote!(Into::<super::TokenStream>::into(
|
||||
Clone::clone(&(@ tree))),));
|
||||
}
|
||||
TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
|
||||
_ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
|
||||
}
|
||||
} else if let TokenTree::Punct(ref tt) = tree {
|
||||
if tt.as_char() == '$' {
|
||||
after_dollar = true;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(quote!(super::TokenStream::from((@ match tree {
|
||||
TokenTree::Punct(tt) => quote!(super::TokenTree::Punct(super::Punct::new(
|
||||
(@ TokenTree::from(Literal::character(tt.as_char()))),
|
||||
(@ match tt.spacing() {
|
||||
Spacing::Alone => quote!(super::Spacing::Alone),
|
||||
Spacing::Joint => quote!(super::Spacing::Joint),
|
||||
}),
|
||||
))),
|
||||
TokenTree::Group(tt) => quote!(super::TokenTree::Group(super::Group::new(
|
||||
(@ match tt.delimiter() {
|
||||
Delimiter::Parenthesis => quote!(super::Delimiter::Parenthesis),
|
||||
Delimiter::Brace => quote!(super::Delimiter::Brace),
|
||||
Delimiter::Bracket => quote!(super::Delimiter::Bracket),
|
||||
Delimiter::None => quote!(super::Delimiter::None),
|
||||
}),
|
||||
(@ quote(tt.stream())),
|
||||
))),
|
||||
TokenTree::Ident(tt) => quote!(super::TokenTree::Ident(super::Ident::new(
|
||||
(@ TokenTree::from(Literal::string(&tt.to_string()))),
|
||||
(@ quote_span(proc_macro_crate.clone(), tt.span())),
|
||||
))),
|
||||
TokenTree::Literal(tt) => quote!(super::TokenTree::Literal({
|
||||
let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
|
||||
.parse::<super::TokenStream>()
|
||||
.unwrap()
|
||||
.into_iter();
|
||||
if let (Some(super::TokenTree::Literal(mut lit)), None) =
|
||||
(iter.next(), iter.next())
|
||||
{
|
||||
lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
|
||||
lit
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}))
|
||||
})),))
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
if after_dollar {
|
||||
panic!("unexpected trailing `$` in `quote!`");
|
||||
}
|
||||
|
||||
quote!([(@ tokens)].iter().cloned().collect::<super::TokenStream>())
|
||||
}
|
||||
|
||||
/// Quote a `Span` into a `TokenStream`.
|
||||
/// This is needed to implement a custom quoter.
|
||||
pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream {
|
||||
let id = span.save_span();
|
||||
quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id)))))
|
||||
}
|
@ -1,840 +0,0 @@
|
||||
//! Rustc proc-macro server implementation with tt
|
||||
//!
|
||||
//! Based on idea from <https://github.com/fedochet/rust-proc-macro-expander>
|
||||
//! The lib-proc-macro server backend is `TokenStream`-agnostic, such that
|
||||
//! we could provide any TokenStream implementation.
|
||||
//! The original idea from fedochet is using proc-macro2 as backend,
|
||||
//! we use tt instead for better integration with RA.
|
||||
//!
|
||||
//! FIXME: No span and source file information is implemented yet
|
||||
|
||||
use super::proc_macro::bridge::{self, server};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Bound;
|
||||
use std::{ascii, vec::IntoIter};
|
||||
|
||||
use crate::tt;
|
||||
|
||||
type Group = tt::Subtree;
|
||||
type TokenTree = tt::TokenTree;
|
||||
type Punct = tt::Punct;
|
||||
type Spacing = tt::Spacing;
|
||||
type Literal = tt::Literal;
|
||||
type Span = tt::TokenId;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TokenStream {
|
||||
pub token_trees: Vec<TokenTree>,
|
||||
}
|
||||
|
||||
impl TokenStream {
|
||||
pub fn new() -> Self {
|
||||
TokenStream::default()
|
||||
}
|
||||
|
||||
pub fn with_subtree(subtree: tt::Subtree) -> Self {
|
||||
if subtree.delimiter.kind != tt::DelimiterKind::Invisible {
|
||||
TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] }
|
||||
} else {
|
||||
TokenStream { token_trees: subtree.token_trees }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_subtree(self) -> tt::Subtree {
|
||||
tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: self.token_trees }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.token_trees.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a token stream containing a single token tree.
|
||||
impl From<TokenTree> for TokenStream {
|
||||
fn from(tree: TokenTree) -> TokenStream {
|
||||
TokenStream { token_trees: vec![tree] }
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects a number of token trees into a single stream.
|
||||
impl FromIterator<TokenTree> for TokenStream {
|
||||
fn from_iter<I: IntoIterator<Item = TokenTree>>(trees: I) -> Self {
|
||||
trees.into_iter().map(TokenStream::from).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A "flattening" operation on token streams, collects token trees
|
||||
/// from multiple token streams into a single stream.
|
||||
impl FromIterator<TokenStream> for TokenStream {
|
||||
fn from_iter<I: IntoIterator<Item = TokenStream>>(streams: I) -> Self {
|
||||
let mut builder = TokenStreamBuilder::new();
|
||||
streams.into_iter().for_each(|stream| builder.push(stream));
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<TokenTree> for TokenStream {
|
||||
fn extend<I: IntoIterator<Item = TokenTree>>(&mut self, trees: I) {
|
||||
self.extend(trees.into_iter().map(TokenStream::from));
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<TokenStream> for TokenStream {
|
||||
fn extend<I: IntoIterator<Item = TokenStream>>(&mut self, streams: I) {
|
||||
for item in streams {
|
||||
for tkn in item {
|
||||
match tkn {
|
||||
tt::TokenTree::Subtree(subtree)
|
||||
if subtree.delimiter.kind == tt::DelimiterKind::Invisible =>
|
||||
{
|
||||
self.token_trees.extend(subtree.token_trees);
|
||||
}
|
||||
_ => {
|
||||
self.token_trees.push(tkn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SourceFile {
|
||||
// FIXME stub
|
||||
}
|
||||
|
||||
type Level = super::proc_macro::Level;
|
||||
type LineColumn = super::proc_macro::LineColumn;
|
||||
|
||||
/// A structure representing a diagnostic message and associated children
|
||||
/// messages.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Diagnostic {
|
||||
level: Level,
|
||||
message: String,
|
||||
spans: Vec<Span>,
|
||||
children: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Creates a new diagnostic with the given `level` and `message`.
|
||||
pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
|
||||
Diagnostic { level, message: message.into(), spans: vec![], children: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
// Rustc Server Ident has to be `Copyable`
|
||||
// We use a stub here for bypassing
|
||||
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
|
||||
pub struct IdentId(u32);
|
||||
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
struct IdentData(tt::Ident);
|
||||
|
||||
#[derive(Default)]
|
||||
struct IdentInterner {
|
||||
idents: HashMap<IdentData, u32>,
|
||||
ident_data: Vec<IdentData>,
|
||||
}
|
||||
|
||||
impl IdentInterner {
|
||||
fn intern(&mut self, data: &IdentData) -> u32 {
|
||||
if let Some(index) = self.idents.get(data) {
|
||||
return *index;
|
||||
}
|
||||
|
||||
let index = self.idents.len() as u32;
|
||||
self.ident_data.push(data.clone());
|
||||
self.idents.insert(data.clone(), index);
|
||||
index
|
||||
}
|
||||
|
||||
fn get(&self, index: u32) -> &IdentData {
|
||||
&self.ident_data[index as usize]
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn get_mut(&mut self, index: u32) -> &mut IdentData {
|
||||
self.ident_data.get_mut(index as usize).expect("Should be consistent")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenStreamBuilder {
|
||||
acc: TokenStream,
|
||||
}
|
||||
|
||||
/// Public implementation details for the `TokenStream` type, such as iterators.
|
||||
pub mod token_stream {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{tt, TokenStream, TokenTree};
|
||||
|
||||
/// An iterator over `TokenStream`'s `TokenTree`s.
|
||||
/// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups,
|
||||
/// and returns whole groups as token trees.
|
||||
impl IntoIterator for TokenStream {
|
||||
type Item = TokenTree;
|
||||
type IntoIter = super::IntoIter<TokenTree>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.token_trees.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
type LexError = String;
|
||||
|
||||
/// Attempts to break the string into tokens and parse those tokens into a token stream.
|
||||
/// May fail for a number of reasons, for example, if the string contains unbalanced delimiters
|
||||
/// or characters not existing in the language.
|
||||
/// All tokens in the parsed stream get `Span::call_site()` spans.
|
||||
///
|
||||
/// NOTE: some errors may cause panics instead of returning `LexError`. We reserve the right to
|
||||
/// change these errors into `LexError`s later.
|
||||
impl FromStr for TokenStream {
|
||||
type Err = LexError;
|
||||
|
||||
fn from_str(src: &str) -> Result<TokenStream, LexError> {
|
||||
let (subtree, _token_map) =
|
||||
mbe::parse_to_token_tree(src).ok_or("Failed to parse from mbe")?;
|
||||
|
||||
let subtree = subtree_replace_token_ids_with_unspecified(subtree);
|
||||
Ok(TokenStream::with_subtree(subtree))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for TokenStream {
|
||||
fn to_string(&self) -> String {
|
||||
::tt::pretty(&self.token_trees)
|
||||
}
|
||||
}
|
||||
|
||||
fn subtree_replace_token_ids_with_unspecified(subtree: tt::Subtree) -> tt::Subtree {
|
||||
tt::Subtree {
|
||||
delimiter: tt::Delimiter {
|
||||
open: tt::TokenId::UNSPECIFIED,
|
||||
close: tt::TokenId::UNSPECIFIED,
|
||||
..subtree.delimiter
|
||||
},
|
||||
token_trees: subtree
|
||||
.token_trees
|
||||
.into_iter()
|
||||
.map(token_tree_replace_token_ids_with_unspecified)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn token_tree_replace_token_ids_with_unspecified(tt: tt::TokenTree) -> tt::TokenTree {
|
||||
match tt {
|
||||
tt::TokenTree::Leaf(leaf) => {
|
||||
tt::TokenTree::Leaf(leaf_replace_token_ids_with_unspecified(leaf))
|
||||
}
|
||||
tt::TokenTree::Subtree(subtree) => {
|
||||
tt::TokenTree::Subtree(subtree_replace_token_ids_with_unspecified(subtree))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn leaf_replace_token_ids_with_unspecified(leaf: tt::Leaf) -> tt::Leaf {
|
||||
match leaf {
|
||||
tt::Leaf::Literal(lit) => {
|
||||
tt::Leaf::Literal(tt::Literal { span: tt::TokenId::unspecified(), ..lit })
|
||||
}
|
||||
tt::Leaf::Punct(punct) => {
|
||||
tt::Leaf::Punct(tt::Punct { span: tt::TokenId::unspecified(), ..punct })
|
||||
}
|
||||
tt::Leaf::Ident(ident) => {
|
||||
tt::Leaf::Ident(tt::Ident { span: tt::TokenId::unspecified(), ..ident })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenStreamBuilder {
|
||||
fn new() -> TokenStreamBuilder {
|
||||
TokenStreamBuilder { acc: TokenStream::new() }
|
||||
}
|
||||
|
||||
fn push(&mut self, stream: TokenStream) {
|
||||
self.acc.extend(stream.into_iter())
|
||||
}
|
||||
|
||||
fn build(self) -> TokenStream {
|
||||
self.acc
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FreeFunctions;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TokenStreamIter {
|
||||
trees: IntoIter<TokenTree>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RustAnalyzer {
|
||||
ident_interner: IdentInterner,
|
||||
// FIXME: store span information here.
|
||||
}
|
||||
|
||||
impl server::Types for RustAnalyzer {
|
||||
type FreeFunctions = FreeFunctions;
|
||||
type TokenStream = TokenStream;
|
||||
type Group = Group;
|
||||
type Punct = Punct;
|
||||
type Ident = IdentId;
|
||||
type Literal = Literal;
|
||||
type SourceFile = SourceFile;
|
||||
type Diagnostic = Diagnostic;
|
||||
type Span = Span;
|
||||
type MultiSpan = Vec<Span>;
|
||||
}
|
||||
|
||||
impl server::FreeFunctions for RustAnalyzer {
|
||||
fn track_env_var(&mut self, _var: &str, _value: Option<&str>) {
|
||||
// FIXME: track env var accesses
|
||||
// https://github.com/rust-lang/rust/pull/71858
|
||||
}
|
||||
fn track_path(&mut self, _path: &str) {}
|
||||
}
|
||||
|
||||
impl server::TokenStream for RustAnalyzer {
|
||||
fn is_empty(&mut self, stream: &Self::TokenStream) -> bool {
|
||||
stream.is_empty()
|
||||
}
|
||||
fn from_str(&mut self, src: &str) -> Self::TokenStream {
|
||||
use std::str::FromStr;
|
||||
|
||||
Self::TokenStream::from_str(src).expect("cannot parse string")
|
||||
}
|
||||
fn to_string(&mut self, stream: &Self::TokenStream) -> String {
|
||||
stream.to_string()
|
||||
}
|
||||
fn from_token_tree(
|
||||
&mut self,
|
||||
tree: bridge::TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>,
|
||||
) -> Self::TokenStream {
|
||||
match tree {
|
||||
bridge::TokenTree::Group(group) => {
|
||||
let tree = TokenTree::from(group);
|
||||
Self::TokenStream::from_iter(vec![tree])
|
||||
}
|
||||
|
||||
bridge::TokenTree::Ident(IdentId(index)) => {
|
||||
let IdentData(ident) = self.ident_interner.get(index).clone();
|
||||
let ident: tt::Ident = ident;
|
||||
let leaf = tt::Leaf::from(ident);
|
||||
let tree = TokenTree::from(leaf);
|
||||
Self::TokenStream::from_iter(vec![tree])
|
||||
}
|
||||
|
||||
bridge::TokenTree::Literal(literal) => {
|
||||
let leaf = tt::Leaf::from(literal);
|
||||
let tree = TokenTree::from(leaf);
|
||||
Self::TokenStream::from_iter(vec![tree])
|
||||
}
|
||||
|
||||
bridge::TokenTree::Punct(p) => {
|
||||
let leaf = tt::Leaf::from(p);
|
||||
let tree = TokenTree::from(leaf);
|
||||
Self::TokenStream::from_iter(vec![tree])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result<Self::TokenStream, ()> {
|
||||
Ok(self_.clone())
|
||||
}
|
||||
|
||||
fn concat_trees(
|
||||
&mut self,
|
||||
base: Option<Self::TokenStream>,
|
||||
trees: Vec<bridge::TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>>,
|
||||
) -> Self::TokenStream {
|
||||
let mut builder = TokenStreamBuilder::new();
|
||||
if let Some(base) = base {
|
||||
builder.push(base);
|
||||
}
|
||||
for tree in trees {
|
||||
builder.push(self.from_token_tree(tree));
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn concat_streams(
|
||||
&mut self,
|
||||
base: Option<Self::TokenStream>,
|
||||
streams: Vec<Self::TokenStream>,
|
||||
) -> Self::TokenStream {
|
||||
let mut builder = TokenStreamBuilder::new();
|
||||
if let Some(base) = base {
|
||||
builder.push(base);
|
||||
}
|
||||
for stream in streams {
|
||||
builder.push(stream);
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn into_trees(
|
||||
&mut self,
|
||||
stream: Self::TokenStream,
|
||||
) -> Vec<bridge::TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>> {
|
||||
stream
|
||||
.into_iter()
|
||||
.map(|tree| match tree {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
|
||||
bridge::TokenTree::Ident(IdentId(self.ident_interner.intern(&IdentData(ident))))
|
||||
}
|
||||
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => bridge::TokenTree::Literal(lit),
|
||||
tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => bridge::TokenTree::Punct(punct),
|
||||
tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(subtree),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn delim_to_internal(d: bridge::Delimiter) -> tt::Delimiter {
|
||||
let kind = match d {
|
||||
bridge::Delimiter::Parenthesis => tt::DelimiterKind::Parenthesis,
|
||||
bridge::Delimiter::Brace => tt::DelimiterKind::Brace,
|
||||
bridge::Delimiter::Bracket => tt::DelimiterKind::Bracket,
|
||||
bridge::Delimiter::None => tt::DelimiterKind::Invisible,
|
||||
};
|
||||
tt::Delimiter { open: tt::TokenId::unspecified(), close: tt::TokenId::unspecified(), kind }
|
||||
}
|
||||
|
||||
fn delim_to_external(d: tt::Delimiter) -> bridge::Delimiter {
|
||||
match d.kind {
|
||||
tt::DelimiterKind::Parenthesis => bridge::Delimiter::Parenthesis,
|
||||
tt::DelimiterKind::Brace => bridge::Delimiter::Brace,
|
||||
tt::DelimiterKind::Bracket => bridge::Delimiter::Bracket,
|
||||
tt::DelimiterKind::Invisible => bridge::Delimiter::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn spacing_to_internal(spacing: bridge::Spacing) -> Spacing {
|
||||
match spacing {
|
||||
bridge::Spacing::Alone => Spacing::Alone,
|
||||
bridge::Spacing::Joint => Spacing::Joint,
|
||||
}
|
||||
}
|
||||
|
||||
fn spacing_to_external(spacing: Spacing) -> bridge::Spacing {
|
||||
match spacing {
|
||||
Spacing::Alone => bridge::Spacing::Alone,
|
||||
Spacing::Joint => bridge::Spacing::Joint,
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Group for RustAnalyzer {
|
||||
fn new(
|
||||
&mut self,
|
||||
delimiter: bridge::Delimiter,
|
||||
stream: Option<Self::TokenStream>,
|
||||
) -> Self::Group {
|
||||
Self::Group {
|
||||
delimiter: delim_to_internal(delimiter),
|
||||
token_trees: stream.unwrap_or_default().token_trees,
|
||||
}
|
||||
}
|
||||
fn delimiter(&mut self, group: &Self::Group) -> bridge::Delimiter {
|
||||
delim_to_external(group.delimiter)
|
||||
}
|
||||
|
||||
// NOTE: Return value of do not include delimiter
|
||||
fn stream(&mut self, group: &Self::Group) -> Self::TokenStream {
|
||||
TokenStream { token_trees: group.token_trees.clone() }
|
||||
}
|
||||
|
||||
fn span(&mut self, group: &Self::Group) -> Self::Span {
|
||||
group.delimiter.open
|
||||
}
|
||||
|
||||
fn set_span(&mut self, group: &mut Self::Group, span: Self::Span) {
|
||||
group.delimiter.open = span;
|
||||
}
|
||||
|
||||
fn span_open(&mut self, group: &Self::Group) -> Self::Span {
|
||||
group.delimiter.open
|
||||
}
|
||||
|
||||
fn span_close(&mut self, group: &Self::Group) -> Self::Span {
|
||||
group.delimiter.close
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Punct for RustAnalyzer {
|
||||
fn new(&mut self, ch: char, spacing: bridge::Spacing) -> Self::Punct {
|
||||
tt::Punct {
|
||||
char: ch,
|
||||
spacing: spacing_to_internal(spacing),
|
||||
span: tt::TokenId::unspecified(),
|
||||
}
|
||||
}
|
||||
fn as_char(&mut self, punct: Self::Punct) -> char {
|
||||
punct.char
|
||||
}
|
||||
fn spacing(&mut self, punct: Self::Punct) -> bridge::Spacing {
|
||||
spacing_to_external(punct.spacing)
|
||||
}
|
||||
fn span(&mut self, punct: Self::Punct) -> Self::Span {
|
||||
punct.span
|
||||
}
|
||||
fn with_span(&mut self, punct: Self::Punct, span: Self::Span) -> Self::Punct {
|
||||
tt::Punct { span: span, ..punct }
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Ident for RustAnalyzer {
|
||||
fn new(&mut self, string: &str, span: Self::Span, is_raw: bool) -> Self::Ident {
|
||||
IdentId(self.ident_interner.intern(&IdentData(tt::Ident {
|
||||
text: if is_raw { ::tt::SmolStr::from_iter(["r#", string]) } else { string.into() },
|
||||
span,
|
||||
})))
|
||||
}
|
||||
|
||||
fn span(&mut self, ident: Self::Ident) -> Self::Span {
|
||||
self.ident_interner.get(ident.0).0.span
|
||||
}
|
||||
fn with_span(&mut self, ident: Self::Ident, span: Self::Span) -> Self::Ident {
|
||||
let data = self.ident_interner.get(ident.0);
|
||||
let new = IdentData(tt::Ident { span: span, ..data.0.clone() });
|
||||
IdentId(self.ident_interner.intern(&new))
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Literal for RustAnalyzer {
|
||||
fn debug_kind(&mut self, _literal: &Self::Literal) -> String {
|
||||
// r-a: debug_kind and suffix are unsupported; corresponding client code has been changed to not call these.
|
||||
// They must still be present to be ABI-compatible and work with upstream proc_macro.
|
||||
"".to_owned()
|
||||
}
|
||||
fn from_str(&mut self, s: &str) -> Result<Self::Literal, ()> {
|
||||
Ok(Literal { text: s.into(), span: tt::TokenId::unspecified() })
|
||||
}
|
||||
fn symbol(&mut self, literal: &Self::Literal) -> String {
|
||||
literal.text.to_string()
|
||||
}
|
||||
fn suffix(&mut self, _literal: &Self::Literal) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_string(&mut self, literal: &Self::Literal) -> String {
|
||||
literal.to_string()
|
||||
}
|
||||
|
||||
fn integer(&mut self, n: &str) -> Self::Literal {
|
||||
let n = match n.parse::<i128>() {
|
||||
Ok(n) => n.to_string(),
|
||||
Err(_) => n.parse::<u128>().unwrap().to_string(),
|
||||
};
|
||||
Literal { text: n.into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn typed_integer(&mut self, n: &str, kind: &str) -> Self::Literal {
|
||||
macro_rules! def_suffixed_integer {
|
||||
($kind:ident, $($ty:ty),*) => {
|
||||
match $kind {
|
||||
$(
|
||||
stringify!($ty) => {
|
||||
let n: $ty = n.parse().unwrap();
|
||||
format!(concat!("{}", stringify!($ty)), n)
|
||||
}
|
||||
)*
|
||||
_ => unimplemented!("unknown args for typed_integer: n {}, kind {}", n, $kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let text = def_suffixed_integer! {kind, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize};
|
||||
|
||||
Literal { text: text.into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn float(&mut self, n: &str) -> Self::Literal {
|
||||
let n: f64 = n.parse().unwrap();
|
||||
let mut text = f64::to_string(&n);
|
||||
if !text.contains('.') {
|
||||
text += ".0"
|
||||
}
|
||||
Literal { text: text.into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn f32(&mut self, n: &str) -> Self::Literal {
|
||||
let n: f32 = n.parse().unwrap();
|
||||
let text = format!("{n}f32");
|
||||
Literal { text: text.into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn f64(&mut self, n: &str) -> Self::Literal {
|
||||
let n: f64 = n.parse().unwrap();
|
||||
let text = format!("{n}f64");
|
||||
Literal { text: text.into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn string(&mut self, string: &str) -> Self::Literal {
|
||||
let mut escaped = String::new();
|
||||
for ch in string.chars() {
|
||||
escaped.extend(ch.escape_debug());
|
||||
}
|
||||
Literal { text: format!("\"{escaped}\"").into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn character(&mut self, ch: char) -> Self::Literal {
|
||||
Literal { text: format!("'{ch}'").into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn byte_string(&mut self, bytes: &[u8]) -> Self::Literal {
|
||||
let string = bytes
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(ascii::escape_default)
|
||||
.map(Into::<char>::into)
|
||||
.collect::<String>();
|
||||
|
||||
Literal { text: format!("b\"{string}\"").into(), span: tt::TokenId::unspecified() }
|
||||
}
|
||||
|
||||
fn span(&mut self, literal: &Self::Literal) -> Self::Span {
|
||||
literal.span
|
||||
}
|
||||
|
||||
fn set_span(&mut self, literal: &mut Self::Literal, span: Self::Span) {
|
||||
literal.span = span;
|
||||
}
|
||||
|
||||
fn subspan(
|
||||
&mut self,
|
||||
_literal: &Self::Literal,
|
||||
_start: Bound<usize>,
|
||||
_end: Bound<usize>,
|
||||
) -> Option<Self::Span> {
|
||||
// FIXME handle span
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl server::SourceFile for RustAnalyzer {
|
||||
// FIXME these are all stubs
|
||||
fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool {
|
||||
true
|
||||
}
|
||||
fn path(&mut self, _file: &Self::SourceFile) -> String {
|
||||
String::new()
|
||||
}
|
||||
fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Diagnostic for RustAnalyzer {
|
||||
fn new(&mut self, level: Level, msg: &str, spans: Self::MultiSpan) -> Self::Diagnostic {
|
||||
let mut diag = Diagnostic::new(level, msg);
|
||||
diag.spans = spans;
|
||||
diag
|
||||
}
|
||||
|
||||
fn sub(
|
||||
&mut self,
|
||||
_diag: &mut Self::Diagnostic,
|
||||
_level: Level,
|
||||
_msg: &str,
|
||||
_spans: Self::MultiSpan,
|
||||
) {
|
||||
// FIXME handle diagnostic
|
||||
//
|
||||
}
|
||||
|
||||
fn emit(&mut self, _diag: Self::Diagnostic) {
|
||||
// FIXME handle diagnostic
|
||||
// diag.emit()
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Span for RustAnalyzer {
|
||||
fn debug(&mut self, span: Self::Span) -> String {
|
||||
format!("{:?}", span.0)
|
||||
}
|
||||
fn def_site(&mut self) -> Self::Span {
|
||||
// MySpan(self.span_interner.intern(&MySpanData(Span::def_site())))
|
||||
// FIXME handle span
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
fn call_site(&mut self) -> Self::Span {
|
||||
// MySpan(self.span_interner.intern(&MySpanData(Span::call_site())))
|
||||
// FIXME handle span
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile {
|
||||
SourceFile {}
|
||||
}
|
||||
fn save_span(&mut self, _span: Self::Span) -> usize {
|
||||
// FIXME stub
|
||||
0
|
||||
}
|
||||
fn recover_proc_macro_span(&mut self, _id: usize) -> Self::Span {
|
||||
// FIXME stub
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
/// Recent feature, not yet in the proc_macro
|
||||
///
|
||||
/// See PR:
|
||||
/// https://github.com/rust-lang/rust/pull/55780
|
||||
fn source_text(&mut self, _span: Self::Span) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn parent(&mut self, _span: Self::Span) -> Option<Self::Span> {
|
||||
// FIXME handle span
|
||||
None
|
||||
}
|
||||
fn source(&mut self, span: Self::Span) -> Self::Span {
|
||||
// FIXME handle span
|
||||
span
|
||||
}
|
||||
fn start(&mut self, _span: Self::Span) -> LineColumn {
|
||||
// FIXME handle span
|
||||
LineColumn { line: 0, column: 0 }
|
||||
}
|
||||
fn end(&mut self, _span: Self::Span) -> LineColumn {
|
||||
// FIXME handle span
|
||||
LineColumn { line: 0, column: 0 }
|
||||
}
|
||||
fn join(&mut self, first: Self::Span, _second: Self::Span) -> Option<Self::Span> {
|
||||
// Just return the first span again, because some macros will unwrap the result.
|
||||
Some(first)
|
||||
}
|
||||
fn resolved_at(&mut self, _span: Self::Span, _at: Self::Span) -> Self::Span {
|
||||
// FIXME handle span
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
|
||||
fn mixed_site(&mut self) -> Self::Span {
|
||||
// FIXME handle span
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
|
||||
fn after(&mut self, _self_: Self::Span) -> Self::Span {
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
|
||||
fn before(&mut self, _self_: Self::Span) -> Self::Span {
|
||||
tt::TokenId::unspecified()
|
||||
}
|
||||
}
|
||||
|
||||
impl server::MultiSpan for RustAnalyzer {
|
||||
fn new(&mut self) -> Self::MultiSpan {
|
||||
// FIXME handle span
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn push(&mut self, other: &mut Self::MultiSpan, span: Self::Span) {
|
||||
//TODP
|
||||
other.push(span)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::proc_macro::bridge::server::Literal;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ra_server_literals() {
|
||||
let mut srv = RustAnalyzer { ident_interner: IdentInterner::default() };
|
||||
assert_eq!(srv.integer("1234").text, "1234");
|
||||
|
||||
assert_eq!(srv.typed_integer("12", "u8").text, "12u8");
|
||||
assert_eq!(srv.typed_integer("255", "u16").text, "255u16");
|
||||
assert_eq!(srv.typed_integer("1234", "u32").text, "1234u32");
|
||||
assert_eq!(srv.typed_integer("15846685", "u64").text, "15846685u64");
|
||||
assert_eq!(srv.typed_integer("15846685258", "u128").text, "15846685258u128");
|
||||
assert_eq!(srv.typed_integer("156788984", "usize").text, "156788984usize");
|
||||
assert_eq!(srv.typed_integer("127", "i8").text, "127i8");
|
||||
assert_eq!(srv.typed_integer("255", "i16").text, "255i16");
|
||||
assert_eq!(srv.typed_integer("1234", "i32").text, "1234i32");
|
||||
assert_eq!(srv.typed_integer("15846685", "i64").text, "15846685i64");
|
||||
assert_eq!(srv.typed_integer("15846685258", "i128").text, "15846685258i128");
|
||||
assert_eq!(srv.float("0").text, "0.0");
|
||||
assert_eq!(srv.float("15684.5867").text, "15684.5867");
|
||||
assert_eq!(srv.f32("15684.58").text, "15684.58f32");
|
||||
assert_eq!(srv.f64("15684.58").text, "15684.58f64");
|
||||
|
||||
assert_eq!(srv.string("hello_world").text, "\"hello_world\"");
|
||||
assert_eq!(srv.character('c').text, "'c'");
|
||||
assert_eq!(srv.byte_string(b"1234586\x88").text, "b\"1234586\\x88\"");
|
||||
|
||||
// u128::max
|
||||
assert_eq!(
|
||||
srv.integer("340282366920938463463374607431768211455").text,
|
||||
"340282366920938463463374607431768211455"
|
||||
);
|
||||
// i128::min
|
||||
assert_eq!(
|
||||
srv.integer("-170141183460469231731687303715884105728").text,
|
||||
"-170141183460469231731687303715884105728"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ra_server_to_string() {
|
||||
let s = TokenStream {
|
||||
token_trees: vec![
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: "struct".into(),
|
||||
span: tt::TokenId::unspecified(),
|
||||
})),
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: "T".into(),
|
||||
span: tt::TokenId::unspecified(),
|
||||
})),
|
||||
tt::TokenTree::Subtree(tt::Subtree {
|
||||
delimiter: tt::Delimiter {
|
||||
open: tt::TokenId::unspecified(),
|
||||
close: tt::TokenId::unspecified(),
|
||||
kind: tt::DelimiterKind::Brace,
|
||||
},
|
||||
token_trees: vec![],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(s.to_string(), "struct T {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ra_server_from_str() {
|
||||
use std::str::FromStr;
|
||||
let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree {
|
||||
delimiter: tt::Delimiter {
|
||||
open: tt::TokenId::unspecified(),
|
||||
close: tt::TokenId::unspecified(),
|
||||
kind: tt::DelimiterKind::Parenthesis,
|
||||
},
|
||||
token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: "a".into(),
|
||||
span: tt::TokenId::unspecified(),
|
||||
}))],
|
||||
});
|
||||
|
||||
let t1 = TokenStream::from_str("(a)").unwrap();
|
||||
assert_eq!(t1.token_trees.len(), 1);
|
||||
assert_eq!(t1.token_trees[0], subtree_paren_a);
|
||||
|
||||
let t2 = TokenStream::from_str("(a);").unwrap();
|
||||
assert_eq!(t2.token_trees.len(), 2);
|
||||
assert_eq!(t2.token_trees[0], subtree_paren_a);
|
||||
|
||||
let underscore = TokenStream::from_str("_").unwrap();
|
||||
assert_eq!(
|
||||
underscore.token_trees[0],
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: "_".into(),
|
||||
span: tt::TokenId::unspecified(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
//! Procedural macros are implemented by compiling the macro providing crate
|
||||
//! to a dynamic library with a particular ABI which the compiler uses to expand
|
||||
//! macros. Unfortunately this ABI is not specified and can change from version
|
||||
//! to version of the compiler. To support this we copy the ABI from the rust
|
||||
//! compiler into submodules of this module (e.g proc_macro_srv::abis::abi_1_47).
|
||||
//!
|
||||
//! All of these ABIs are subsumed in the `Abi` enum, which exposes a simple
|
||||
//! interface the rest of rust-analyzer can use to talk to the macro
|
||||
//! provider.
|
||||
//!
|
||||
//! # Adding a new ABI
|
||||
//!
|
||||
//! To add a new ABI you'll need to copy the source of the target proc_macro
|
||||
//! crate from the source tree of the Rust compiler into this directory tree.
|
||||
//! Then you'll need to modify it
|
||||
//! - Remove any feature! or other things which won't compile on stable
|
||||
//! - change any absolute imports to relative imports within the ABI tree
|
||||
//!
|
||||
//! Then you'll need to add a branch to the `Abi` enum and an implementation of
|
||||
//! `Abi::expand`, `Abi::list_macros` and `Abi::from_lib` for the new ABI. See
|
||||
//! `proc_macro_srv/src/abis/abi_1_47/mod.rs` for an example. Finally you'll
|
||||
//! need to update the conditionals in `Abi::from_lib` to return your new ABI
|
||||
//! for the relevant versions of the rust compiler
|
||||
//!
|
||||
|
||||
mod abi_1_63;
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
mod abi_sysroot;
|
||||
|
||||
// see `build.rs`
|
||||
include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
|
||||
|
||||
// Used by `test/utils.rs`
|
||||
#[cfg(all(test, feature = "sysroot-abi"))]
|
||||
pub(crate) use abi_sysroot::TokenStream as TestTokenStream;
|
||||
|
||||
use super::dylib::LoadProcMacroDylibError;
|
||||
pub(crate) use abi_1_63::Abi as Abi_1_63;
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
pub(crate) use abi_sysroot::Abi as Abi_Sysroot;
|
||||
use libloading::Library;
|
||||
use proc_macro_api::{ProcMacroKind, RustCInfo};
|
||||
|
||||
use crate::tt;
|
||||
|
||||
pub struct PanicMessage {
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl PanicMessage {
|
||||
pub fn as_str(&self) -> Option<String> {
|
||||
self.message.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Abi {
|
||||
Abi1_63(Abi_1_63),
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
AbiSysroot(Abi_Sysroot),
|
||||
}
|
||||
|
||||
impl Abi {
|
||||
/// Load a new ABI.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// *`lib` - The dynamic library containing the macro implementations
|
||||
/// *`symbol_name` - The symbol name the macros can be found attributes
|
||||
/// *`info` - RustCInfo about the compiler that was used to compile the
|
||||
/// macro crate. This is the information we use to figure out
|
||||
/// which ABI to return
|
||||
pub fn from_lib(
|
||||
lib: &Library,
|
||||
symbol_name: String,
|
||||
info: RustCInfo,
|
||||
) -> Result<Abi, LoadProcMacroDylibError> {
|
||||
// the sysroot ABI relies on `extern proc_macro` with unstable features,
|
||||
// instead of a snapshot of the proc macro bridge's source code. it's only
|
||||
// enabled if we have an exact version match.
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
{
|
||||
if info.version_string == RUSTC_VERSION_STRING {
|
||||
let inner = unsafe { Abi_Sysroot::from_lib(lib, symbol_name) }?;
|
||||
return Ok(Abi::AbiSysroot(inner));
|
||||
}
|
||||
|
||||
// if we reached this point, versions didn't match. in testing, we
|
||||
// want that to panic - this could mean that the format of `rustc
|
||||
// --version` no longer matches the format of the version string
|
||||
// stored in the `.rustc` section, and we want to catch that in-tree
|
||||
// with `x.py test`
|
||||
#[cfg(test)]
|
||||
{
|
||||
let allow_mismatch = std::env::var("PROC_MACRO_SRV_ALLOW_SYSROOT_MISMATCH");
|
||||
if let Ok("1") = allow_mismatch.as_deref() {
|
||||
// only used by rust-analyzer developers, when working on the
|
||||
// sysroot ABI from the rust-analyzer repository - which should
|
||||
// only happen pre-subtree. this can be removed later.
|
||||
} else {
|
||||
panic!(
|
||||
"sysroot ABI mismatch: dylib rustc version (read from .rustc section): {:?} != proc-macro-srv version (read from 'rustc --version'): {:?}",
|
||||
info.version_string, RUSTC_VERSION_STRING
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this should use exclusive ranges when they're stable
|
||||
// https://github.com/rust-lang/rust/issues/37854
|
||||
match (info.version.0, info.version.1) {
|
||||
(1, 63) => {
|
||||
let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?;
|
||||
Ok(Abi::Abi1_63(inner))
|
||||
}
|
||||
_ => Err(LoadProcMacroDylibError::UnsupportedABI(info.version_string)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand(
|
||||
&self,
|
||||
macro_name: &str,
|
||||
macro_body: &tt::Subtree,
|
||||
attributes: Option<&tt::Subtree>,
|
||||
) -> Result<tt::Subtree, PanicMessage> {
|
||||
match self {
|
||||
Self::Abi1_63(abi) => abi.expand(macro_name, macro_body, attributes),
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
Self::AbiSysroot(abi) => abi.expand(macro_name, macro_body, attributes),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
match self {
|
||||
Self::Abi1_63(abi) => abi.list_macros(),
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
Self::AbiSysroot(abi) => abi.list_macros(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_check() {
|
||||
let path = paths::AbsPathBuf::assert(crate::proc_macro_test_dylib_path());
|
||||
let info = proc_macro_api::read_dylib_info(&path).unwrap();
|
||||
assert!(info.version.1 >= 50);
|
||||
}
|
@ -13,10 +13,6 @@
|
||||
use paths::AbsPath;
|
||||
use proc_macro_api::{read_dylib_info, ProcMacroKind};
|
||||
|
||||
use crate::tt;
|
||||
|
||||
use super::abis::Abi;
|
||||
|
||||
const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
|
||||
|
||||
fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
|
||||
@ -82,14 +78,17 @@ fn load_library(file: &Path) -> Result<Library, libloading::Error> {
|
||||
pub enum LoadProcMacroDylibError {
|
||||
Io(io::Error),
|
||||
LibLoading(libloading::Error),
|
||||
UnsupportedABI(String),
|
||||
AbiMismatch(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for LoadProcMacroDylibError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => e.fmt(f),
|
||||
Self::UnsupportedABI(v) => write!(f, "unsupported ABI `{v}`"),
|
||||
Self::AbiMismatch(v) => {
|
||||
use crate::RUSTC_VERSION_STRING;
|
||||
write!(f, "mismatched ABI expected: `{RUSTC_VERSION_STRING}`, got `{v}`")
|
||||
}
|
||||
Self::LibLoading(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
@ -110,7 +109,7 @@ fn from(e: libloading::Error) -> Self {
|
||||
struct ProcMacroLibraryLibloading {
|
||||
// Hold on to the library so it doesn't unload
|
||||
_lib: Library,
|
||||
abi: Abi,
|
||||
proc_macros: crate::proc_macros::ProcMacros,
|
||||
}
|
||||
|
||||
impl ProcMacroLibraryLibloading {
|
||||
@ -125,8 +124,9 @@ fn open(file: &Path) -> Result<Self, LoadProcMacroDylibError> {
|
||||
let version_info = read_dylib_info(abs_file)?;
|
||||
|
||||
let lib = load_library(file).map_err(invalid_data_err)?;
|
||||
let abi = Abi::from_lib(&lib, symbol_name, version_info)?;
|
||||
Ok(ProcMacroLibraryLibloading { _lib: lib, abi })
|
||||
let proc_macros =
|
||||
crate::proc_macros::ProcMacros::from_lib(&lib, symbol_name, version_info)?;
|
||||
Ok(ProcMacroLibraryLibloading { _lib: lib, proc_macros })
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,15 +150,15 @@ pub fn new(lib: &Path) -> Result<Expander, LoadProcMacroDylibError> {
|
||||
pub fn expand(
|
||||
&self,
|
||||
macro_name: &str,
|
||||
macro_body: &tt::Subtree,
|
||||
attributes: Option<&tt::Subtree>,
|
||||
) -> Result<tt::Subtree, String> {
|
||||
let result = self.inner.abi.expand(macro_name, macro_body, attributes);
|
||||
macro_body: &crate::tt::Subtree,
|
||||
attributes: Option<&crate::tt::Subtree>,
|
||||
) -> Result<crate::tt::Subtree, String> {
|
||||
let result = self.inner.proc_macros.expand(macro_name, macro_body, attributes);
|
||||
result.map_err(|e| e.as_str().unwrap_or_else(|| "<unknown error>".to_string()))
|
||||
}
|
||||
|
||||
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
self.inner.abi.list_macros()
|
||||
self.inner.proc_macros.list_macros()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,17 +10,16 @@
|
||||
//! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable`
|
||||
//! rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)…
|
||||
|
||||
#![cfg(feature = "sysroot-abi")]
|
||||
#![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)]
|
||||
#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
|
||||
#![cfg_attr(
|
||||
feature = "sysroot-abi",
|
||||
feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)
|
||||
)]
|
||||
#![allow(unreachable_pub)]
|
||||
|
||||
mod dylib;
|
||||
mod abis;
|
||||
extern crate proc_macro;
|
||||
|
||||
pub mod cli;
|
||||
mod dylib;
|
||||
mod server;
|
||||
mod proc_macros;
|
||||
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
@ -32,25 +31,25 @@
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use proc_macro_api::{
|
||||
msg::{ExpandMacro, FlatTree, PanicMessage},
|
||||
ProcMacroKind,
|
||||
};
|
||||
use proc_macro_api::{msg, ProcMacroKind};
|
||||
|
||||
use ::tt::token_id as tt;
|
||||
|
||||
// see `build.rs`
|
||||
include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ProcMacroSrv {
|
||||
pub struct ProcMacroSrv {
|
||||
expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
|
||||
}
|
||||
|
||||
const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
impl ProcMacroSrv {
|
||||
pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
|
||||
pub fn expand(&mut self, task: msg::ExpandMacro) -> Result<msg::FlatTree, msg::PanicMessage> {
|
||||
let expander = self.expander(task.lib.as_ref()).map_err(|err| {
|
||||
debug_assert!(false, "should list macros before asking to expand");
|
||||
PanicMessage(format!("failed to load macro: {err}"))
|
||||
msg::PanicMessage(format!("failed to load macro: {err}"))
|
||||
})?;
|
||||
|
||||
let prev_env = EnvSnapshot::new();
|
||||
@ -77,7 +76,7 @@ pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
|
||||
.spawn_scoped(s, || {
|
||||
expander
|
||||
.expand(&task.macro_name, ¯o_body, attributes.as_ref())
|
||||
.map(|it| FlatTree::new(&it))
|
||||
.map(|it| msg::FlatTree::new(&it))
|
||||
});
|
||||
let res = match thread {
|
||||
Ok(handle) => handle.join(),
|
||||
@ -102,10 +101,10 @@ pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
|
||||
}
|
||||
}
|
||||
|
||||
result.map_err(PanicMessage)
|
||||
result.map_err(msg::PanicMessage)
|
||||
}
|
||||
|
||||
pub(crate) fn list_macros(
|
||||
pub fn list_macros(
|
||||
&mut self,
|
||||
dylib_path: &Path,
|
||||
) -> Result<Vec<(String, ProcMacroKind)>, String> {
|
||||
@ -129,6 +128,16 @@ fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PanicMessage {
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl PanicMessage {
|
||||
pub fn as_str(&self) -> Option<String> {
|
||||
self.message.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct EnvSnapshot {
|
||||
vars: HashMap<OsString, OsString>,
|
||||
}
|
||||
@ -138,10 +147,13 @@ fn new() -> EnvSnapshot {
|
||||
EnvSnapshot { vars: env::vars_os().collect() }
|
||||
}
|
||||
|
||||
fn rollback(self) {
|
||||
let mut old_vars = self.vars;
|
||||
fn rollback(self) {}
|
||||
}
|
||||
|
||||
impl Drop for EnvSnapshot {
|
||||
fn drop(&mut self) {
|
||||
for (name, value) in env::vars_os() {
|
||||
let old_value = old_vars.remove(&name);
|
||||
let old_value = self.vars.remove(&name);
|
||||
if old_value != Some(value) {
|
||||
match old_value {
|
||||
None => env::remove_var(name),
|
||||
@ -149,13 +161,13 @@ fn rollback(self) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name, old_value) in old_vars {
|
||||
for (name, old_value) in self.vars.drain() {
|
||||
env::set_var(name, old_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "sysroot-abi", test))]
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,45 +1,55 @@
|
||||
//! Proc macro ABI
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[doc(hidden)]
|
||||
mod ra_server;
|
||||
|
||||
use libloading::Library;
|
||||
use proc_macro_api::ProcMacroKind;
|
||||
use proc_macro_api::{ProcMacroKind, RustCInfo};
|
||||
|
||||
use super::{tt, PanicMessage};
|
||||
use crate::{dylib::LoadProcMacroDylibError, server::SYMBOL_INTERNER, tt};
|
||||
|
||||
pub use ra_server::TokenStream;
|
||||
|
||||
pub(crate) struct Abi {
|
||||
pub(crate) struct ProcMacros {
|
||||
exported_macros: Vec<proc_macro::bridge::client::ProcMacro>,
|
||||
}
|
||||
|
||||
impl From<proc_macro::bridge::PanicMessage> for PanicMessage {
|
||||
impl From<proc_macro::bridge::PanicMessage> for crate::PanicMessage {
|
||||
fn from(p: proc_macro::bridge::PanicMessage) -> Self {
|
||||
Self { message: p.as_str().map(|s| s.to_string()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Abi {
|
||||
pub unsafe fn from_lib(lib: &Library, symbol_name: String) -> Result<Abi, libloading::Error> {
|
||||
let macros: libloading::Symbol<'_, &&[proc_macro::bridge::client::ProcMacro]> =
|
||||
lib.get(symbol_name.as_bytes())?;
|
||||
Ok(Self { exported_macros: macros.to_vec() })
|
||||
impl ProcMacros {
|
||||
/// Load a new ABI.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// *`lib` - The dynamic library containing the macro implementations
|
||||
/// *`symbol_name` - The symbol name the macros can be found attributes
|
||||
/// *`info` - RustCInfo about the compiler that was used to compile the
|
||||
/// macro crate. This is the information we use to figure out
|
||||
/// which ABI to return
|
||||
pub(crate) fn from_lib(
|
||||
lib: &Library,
|
||||
symbol_name: String,
|
||||
info: RustCInfo,
|
||||
) -> Result<ProcMacros, LoadProcMacroDylibError> {
|
||||
if info.version_string == crate::RUSTC_VERSION_STRING {
|
||||
let macros = unsafe {
|
||||
lib.get::<&&[proc_macro::bridge::client::ProcMacro]>(symbol_name.as_bytes())
|
||||
}?;
|
||||
|
||||
return Ok(Self { exported_macros: macros.to_vec() });
|
||||
}
|
||||
Err(LoadProcMacroDylibError::AbiMismatch(info.version_string))
|
||||
}
|
||||
|
||||
pub fn expand(
|
||||
pub(crate) fn expand(
|
||||
&self,
|
||||
macro_name: &str,
|
||||
macro_body: &tt::Subtree,
|
||||
attributes: Option<&tt::Subtree>,
|
||||
) -> Result<tt::Subtree, PanicMessage> {
|
||||
let parsed_body = ra_server::TokenStream::with_subtree(macro_body.clone());
|
||||
) -> Result<tt::Subtree, crate::PanicMessage> {
|
||||
let parsed_body = crate::server::TokenStream::with_subtree(macro_body.clone());
|
||||
|
||||
let parsed_attributes = attributes.map_or(ra_server::TokenStream::new(), |attr| {
|
||||
ra_server::TokenStream::with_subtree(attr.clone())
|
||||
let parsed_attributes = attributes.map_or(crate::server::TokenStream::new(), |attr| {
|
||||
crate::server::TokenStream::with_subtree(attr.clone())
|
||||
});
|
||||
|
||||
for proc_macro in &self.exported_macros {
|
||||
@ -49,34 +59,34 @@ pub fn expand(
|
||||
} if *trait_name == macro_name => {
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
crate::server::RustAnalyzer { interner: &SYMBOL_INTERNER },
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
return res.map(|it| it.into_subtree()).map_err(crate::PanicMessage::from);
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Bang { name, client }
|
||||
if *name == macro_name =>
|
||||
{
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
crate::server::RustAnalyzer { interner: &SYMBOL_INTERNER },
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
return res.map(|it| it.into_subtree()).map_err(crate::PanicMessage::from);
|
||||
}
|
||||
proc_macro::bridge::client::ProcMacro::Attr { name, client }
|
||||
if *name == macro_name =>
|
||||
{
|
||||
let res = client.run(
|
||||
&proc_macro::bridge::server::SameThread,
|
||||
ra_server::RustAnalyzer::default(),
|
||||
crate::server::RustAnalyzer { interner: &SYMBOL_INTERNER },
|
||||
parsed_attributes,
|
||||
parsed_body,
|
||||
true,
|
||||
);
|
||||
return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
|
||||
return res.map(|it| it.into_subtree()).map_err(crate::PanicMessage::from);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
@ -85,7 +95,7 @@ pub fn expand(
|
||||
Err(proc_macro::bridge::PanicMessage::String("Nothing to expand".to_string()).into())
|
||||
}
|
||||
|
||||
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
pub(crate) fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
self.exported_macros
|
||||
.iter()
|
||||
.map(|proc_macro| match proc_macro {
|
||||
@ -102,3 +112,16 @@ pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_check() {
|
||||
let path = paths::AbsPathBuf::assert(crate::proc_macro_test_dylib_path());
|
||||
let info = proc_macro_api::read_dylib_info(&path).unwrap();
|
||||
assert_eq!(
|
||||
info.version_string,
|
||||
crate::RUSTC_VERSION_STRING,
|
||||
"sysroot ABI mismatch: dylib rustc version (read from .rustc section): {:?} != proc-macro-srv version (read from 'rustc --version'): {:?}",
|
||||
info.version_string,
|
||||
crate::RUSTC_VERSION_STRING,
|
||||
);
|
||||
}
|
@ -8,9 +8,9 @@
|
||||
//!
|
||||
//! FIXME: No span and source file information is implemented yet
|
||||
|
||||
use super::proc_macro::{
|
||||
self,
|
||||
use proc_macro::{
|
||||
bridge::{self, server},
|
||||
LineColumn,
|
||||
};
|
||||
|
||||
mod token_stream;
|
||||
@ -26,8 +26,10 @@
|
||||
|
||||
type Group = tt::Subtree;
|
||||
type TokenTree = tt::TokenTree;
|
||||
#[allow(unused)]
|
||||
type Punct = tt::Punct;
|
||||
type Spacing = tt::Spacing;
|
||||
#[allow(unused)]
|
||||
type Literal = tt::Literal;
|
||||
type Span = tt::TokenId;
|
||||
|
||||
@ -36,14 +38,11 @@ pub struct SourceFile {
|
||||
// FIXME stub
|
||||
}
|
||||
|
||||
type Level = super::proc_macro::Level;
|
||||
type LineColumn = super::proc_macro::LineColumn;
|
||||
|
||||
pub struct FreeFunctions;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RustAnalyzer {
|
||||
// FIXME: store span information here.
|
||||
pub(crate) interner: SymbolInternerRef,
|
||||
}
|
||||
|
||||
impl server::Types for RustAnalyzer {
|
||||
@ -68,7 +67,7 @@ fn literal_from_str(
|
||||
// FIXME: keep track of LitKind and Suffix
|
||||
Ok(bridge::Literal {
|
||||
kind: bridge::LitKind::Err,
|
||||
symbol: Symbol::intern(s),
|
||||
symbol: Symbol::intern(self.interner, s),
|
||||
suffix: None,
|
||||
span: tt::TokenId::unspecified(),
|
||||
})
|
||||
@ -109,7 +108,7 @@ fn from_token_tree(
|
||||
}
|
||||
|
||||
bridge::TokenTree::Ident(ident) => {
|
||||
let text = ident.sym.text();
|
||||
let text = ident.sym.text(self.interner);
|
||||
let text =
|
||||
if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text };
|
||||
let ident: tt::Ident = tt::Ident { text, span: ident.span };
|
||||
@ -120,8 +119,9 @@ fn from_token_tree(
|
||||
|
||||
bridge::TokenTree::Literal(literal) => {
|
||||
let literal = LiteralFormatter(literal);
|
||||
let text = literal
|
||||
.with_stringify_parts(|parts| ::tt::SmolStr::from_iter(parts.iter().copied()));
|
||||
let text = literal.with_stringify_parts(self.interner, |parts| {
|
||||
::tt::SmolStr::from_iter(parts.iter().copied())
|
||||
});
|
||||
|
||||
let literal = tt::Literal { text, span: literal.0.span };
|
||||
let leaf = tt::Leaf::from(literal);
|
||||
@ -185,7 +185,7 @@ fn into_trees(
|
||||
.map(|tree| match tree {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
|
||||
bridge::TokenTree::Ident(bridge::Ident {
|
||||
sym: Symbol::intern(ident.text.trim_start_matches("r#")),
|
||||
sym: Symbol::intern(self.interner, ident.text.trim_start_matches("r#")),
|
||||
is_raw: ident.text.starts_with("r#"),
|
||||
span: ident.span,
|
||||
})
|
||||
@ -194,7 +194,7 @@ fn into_trees(
|
||||
bridge::TokenTree::Literal(bridge::Literal {
|
||||
// FIXME: handle literal kinds
|
||||
kind: bridge::LitKind::Err,
|
||||
symbol: Symbol::intern(&lit.text),
|
||||
symbol: Symbol::intern(self.interner, &lit.text),
|
||||
// FIXME: handle suffixes
|
||||
suffix: None,
|
||||
span: lit.span,
|
||||
@ -240,6 +240,7 @@ fn delim_to_external(d: tt::Delimiter) -> proc_macro::Delimiter {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn spacing_to_internal(spacing: proc_macro::Spacing) -> Spacing {
|
||||
match spacing {
|
||||
proc_macro::Spacing::Alone => Spacing::Alone,
|
||||
@ -247,6 +248,7 @@ fn spacing_to_internal(spacing: proc_macro::Spacing) -> Spacing {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn spacing_to_external(spacing: Spacing) -> proc_macro::Spacing {
|
||||
match spacing {
|
||||
Spacing::Alone => proc_macro::Spacing::Alone,
|
||||
@ -350,11 +352,13 @@ fn globals(&mut self) -> bridge::ExpnGlobals<Self::Span> {
|
||||
}
|
||||
|
||||
fn intern_symbol(ident: &str) -> Self::Symbol {
|
||||
Symbol::intern(&::tt::SmolStr::from(ident))
|
||||
// FIXME: should be self.interner once the proc-macro api allows is
|
||||
Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident))
|
||||
}
|
||||
|
||||
fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) {
|
||||
f(symbol.text().as_str())
|
||||
// FIXME: should be self.interner once the proc-macro api allows is
|
||||
f(symbol.text(&SYMBOL_INTERNER).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,7 +369,11 @@ impl LiteralFormatter {
|
||||
/// literal's representation. This is done to allow the `ToString` and
|
||||
/// `Display` implementations to borrow references to symbol values, and
|
||||
/// both be optimized to reduce overhead.
|
||||
fn with_stringify_parts<R>(&self, f: impl FnOnce(&[&str]) -> R) -> R {
|
||||
fn with_stringify_parts<R>(
|
||||
&self,
|
||||
interner: SymbolInternerRef,
|
||||
f: impl FnOnce(&[&str]) -> R,
|
||||
) -> R {
|
||||
/// Returns a string containing exactly `num` '#' characters.
|
||||
/// Uses a 256-character source string literal which is always safe to
|
||||
/// index with a `u8` index.
|
||||
@ -380,7 +388,7 @@ fn get_hashes_str(num: u8) -> &'static str {
|
||||
&HASHES[..num as usize]
|
||||
}
|
||||
|
||||
self.with_symbol_and_suffix(|symbol, suffix| match self.0.kind {
|
||||
self.with_symbol_and_suffix(interner, |symbol, suffix| match self.0.kind {
|
||||
bridge::LitKind::Byte => f(&["b'", symbol, "'", suffix]),
|
||||
bridge::LitKind::Char => f(&["'", symbol, "'", suffix]),
|
||||
bridge::LitKind::Str => f(&["\"", symbol, "\"", suffix]),
|
||||
@ -397,9 +405,13 @@ fn get_hashes_str(num: u8) -> &'static str {
|
||||
})
|
||||
}
|
||||
|
||||
fn with_symbol_and_suffix<R>(&self, f: impl FnOnce(&str, &str) -> R) -> R {
|
||||
let symbol = self.0.symbol.text();
|
||||
let suffix = self.0.suffix.map(|s| s.text()).unwrap_or_default();
|
||||
fn with_symbol_and_suffix<R>(
|
||||
&self,
|
||||
interner: SymbolInternerRef,
|
||||
f: impl FnOnce(&str, &str) -> R,
|
||||
) -> R {
|
||||
let symbol = self.0.symbol.text(interner);
|
||||
let suffix = self.0.suffix.map(|s| s.text(interner)).unwrap_or_default();
|
||||
f(symbol.as_str(), suffix.as_str())
|
||||
}
|
||||
}
|
@ -1,28 +1,30 @@
|
||||
//! Symbol interner for proc-macro-srv
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use std::{cell::RefCell, collections::HashMap, thread::LocalKey};
|
||||
use tt::SmolStr;
|
||||
|
||||
thread_local! {
|
||||
static SYMBOL_INTERNER: RefCell<SymbolInterner> = Default::default();
|
||||
pub(crate) static SYMBOL_INTERNER: RefCell<SymbolInterner> = Default::default();
|
||||
}
|
||||
|
||||
// ID for an interned symbol.
|
||||
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
|
||||
pub struct Symbol(u32);
|
||||
|
||||
pub(crate) type SymbolInternerRef = &'static LocalKey<RefCell<SymbolInterner>>;
|
||||
|
||||
impl Symbol {
|
||||
pub fn intern(data: &str) -> Symbol {
|
||||
SYMBOL_INTERNER.with(|i| i.borrow_mut().intern(data))
|
||||
pub(super) fn intern(interner: SymbolInternerRef, data: &str) -> Symbol {
|
||||
interner.with(|i| i.borrow_mut().intern(data))
|
||||
}
|
||||
|
||||
pub fn text(&self) -> SmolStr {
|
||||
SYMBOL_INTERNER.with(|i| i.borrow().get(self).clone())
|
||||
pub(super) fn text(&self, interner: SymbolInternerRef) -> SmolStr {
|
||||
interner.with(|i| i.borrow().get(self).clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SymbolInterner {
|
||||
pub(crate) struct SymbolInterner {
|
||||
idents: HashMap<SmolStr, u32>,
|
||||
ident_data: Vec<SmolStr>,
|
||||
}
|
@ -4,15 +4,15 @@
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TokenStream {
|
||||
pub token_trees: Vec<TokenTree>,
|
||||
pub(super) token_trees: Vec<TokenTree>,
|
||||
}
|
||||
|
||||
impl TokenStream {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
TokenStream::default()
|
||||
}
|
||||
|
||||
pub fn with_subtree(subtree: tt::Subtree) -> Self {
|
||||
pub(crate) fn with_subtree(subtree: tt::Subtree) -> Self {
|
||||
if subtree.delimiter.kind != tt::DelimiterKind::Invisible {
|
||||
TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] }
|
||||
} else {
|
||||
@ -20,11 +20,11 @@ pub fn with_subtree(subtree: tt::Subtree) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_subtree(self) -> tt::Subtree {
|
||||
pub(crate) fn into_subtree(self) -> tt::Subtree {
|
||||
tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: self.token_trees }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.token_trees.is_empty()
|
||||
}
|
||||
}
|
||||
@ -78,12 +78,12 @@ fn extend<I: IntoIterator<Item = TokenStream>>(&mut self, streams: I) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenStreamBuilder {
|
||||
pub(super) struct TokenStreamBuilder {
|
||||
acc: TokenStream,
|
||||
}
|
||||
|
||||
/// Public implementation details for the `TokenStream` type, such as iterators.
|
||||
pub mod token_stream {
|
||||
/// pub(super)lic implementation details for the `TokenStream` type, such as iterators.
|
||||
pub(super) mod token_stream {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{tt, TokenStream, TokenTree};
|
@ -5,14 +5,14 @@
|
||||
|
||||
use crate::{dylib, proc_macro_test_dylib_path, ProcMacroSrv};
|
||||
|
||||
fn parse_string(code: &str) -> Option<crate::abis::TestTokenStream> {
|
||||
fn parse_string(code: &str) -> Option<crate::server::TokenStream> {
|
||||
// This is a bit strange. We need to parse a string into a token stream into
|
||||
// order to create a tt::SubTree from it in fixtures. `into_subtree` is
|
||||
// implemented by all the ABIs we have so we arbitrarily choose one ABI to
|
||||
// write a `parse_string` function for and use that. The tests don't really
|
||||
// care which ABI we're using as the `into_subtree` function isn't part of
|
||||
// the ABI and shouldn't change between ABI versions.
|
||||
crate::abis::TestTokenStream::from_str(code).ok()
|
||||
crate::server::TokenStream::from_str(code).ok()
|
||||
}
|
||||
|
||||
pub fn assert_expand(macro_name: &str, ra_fixture: &str, expect: Expect) {
|
||||
|
@ -67,7 +67,7 @@ ide-db.workspace = true
|
||||
ide-ssr.workspace = true
|
||||
ide.workspace = true
|
||||
proc-macro-api.workspace = true
|
||||
proc-macro-srv.workspace = true
|
||||
proc-macro-srv-cli.workspace = true
|
||||
profile.workspace = true
|
||||
project-model.workspace = true
|
||||
stdx.workspace = true
|
||||
@ -95,8 +95,9 @@ mbe.workspace = true
|
||||
[features]
|
||||
jemalloc = ["jemallocator", "profile/jemalloc"]
|
||||
force-always-assert = ["always-assert/force"]
|
||||
sysroot-abi = ["proc-macro-srv-cli/sysroot-abi"]
|
||||
in-rust-tree = [
|
||||
"proc-macro-srv/sysroot-abi",
|
||||
"sysroot-abi",
|
||||
"ide/in-rust-tree",
|
||||
"syntax/in-rust-tree",
|
||||
]
|
||||
|
@ -77,7 +77,7 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
|
||||
with_extra_thread("LspServer", run_server)?;
|
||||
}
|
||||
flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
|
||||
with_extra_thread("MacroExpander", || proc_macro_srv::cli::run().map_err(Into::into))?;
|
||||
with_extra_thread("MacroExpander", || proc_macro_srv_cli::run().map_err(Into::into))?;
|
||||
}
|
||||
flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
|
||||
|
@ -114,6 +114,10 @@ pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
|
||||
status.health = lsp_ext::Health::Warning;
|
||||
message.push_str("Failed to run build scripts of some packages.\n\n");
|
||||
}
|
||||
if self.proc_macro_clients.iter().any(|it| it.is_err()) {
|
||||
status.health = lsp_ext::Health::Warning;
|
||||
message.push_str("Failed to spawn one or more proc-macro servers.\n\n");
|
||||
}
|
||||
if !self.config.cargo_autoreload()
|
||||
&& self.is_quiescent()
|
||||
&& self.fetch_workspaces_queue.op_requested()
|
||||
@ -384,18 +388,23 @@ pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
|
||||
.workspaces
|
||||
.iter()
|
||||
.map(|ws| {
|
||||
let (path, args): (_, &[_]) = if path_manually_set {
|
||||
let path = if path_manually_set {
|
||||
tracing::debug!(
|
||||
"Pro-macro server path explicitly set: {}",
|
||||
path.display()
|
||||
);
|
||||
(path.clone(), &[])
|
||||
path.clone()
|
||||
} else {
|
||||
match ws.find_sysroot_proc_macro_srv() {
|
||||
Some(server_path) => (server_path, &[]),
|
||||
None => (path.clone(), &["proc-macro"]),
|
||||
Some(server_path) => server_path,
|
||||
None => path.clone(),
|
||||
}
|
||||
};
|
||||
let args: &[_] = if path.file_stem() == Some("rust-analyzer".as_ref()) {
|
||||
&["proc-macro"]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
tracing::info!(?args, "Using proc-macro server at {}", path.display(),);
|
||||
ProcMacroServer::spawn(path.clone(), args).map_err(|err| {
|
||||
|
@ -59,7 +59,7 @@ fn completes_items_from_standard_library() {
|
||||
"#,
|
||||
)
|
||||
.with_config(serde_json::json!({
|
||||
"cargo": { "sysroot": "discover" }
|
||||
"cargo": { "sysroot": "discover" },
|
||||
}))
|
||||
.server()
|
||||
.wait_until_workspace_is_loaded();
|
||||
@ -508,7 +508,7 @@ fn main() {}
|
||||
#[test]
|
||||
fn test_missing_module_code_action_in_json_project() {
|
||||
if skip_slow_tests() {
|
||||
// return;
|
||||
return;
|
||||
}
|
||||
|
||||
let tmp_dir = TestDir::new();
|
||||
@ -612,7 +612,7 @@ fn main() {{}}
|
||||
"#
|
||||
))
|
||||
.with_config(serde_json::json!({
|
||||
"cargo": { "sysroot": "discover" }
|
||||
"cargo": { "sysroot": "discover" },
|
||||
}))
|
||||
.server()
|
||||
.wait_until_workspace_is_loaded();
|
||||
@ -685,7 +685,7 @@ fn preserves_dos_line_endings() {
|
||||
#[test]
|
||||
fn out_dirs_check() {
|
||||
if skip_slow_tests() {
|
||||
// return;
|
||||
return;
|
||||
}
|
||||
|
||||
let server = Project::with_fixture(
|
||||
@ -711,10 +711,21 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
//- /src/main.rs
|
||||
#[rustc_builtin_macro] macro_rules! include {}
|
||||
#[rustc_builtin_macro] macro_rules! include_str {}
|
||||
#[rustc_builtin_macro] macro_rules! concat {}
|
||||
#[rustc_builtin_macro] macro_rules! env {}
|
||||
#![allow(warnings)]
|
||||
#![feature(rustc_attrs)]
|
||||
#[rustc_builtin_macro] macro_rules! include {
|
||||
($file:expr $(,)?) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
#[rustc_builtin_macro] macro_rules! include_str {
|
||||
($file:expr $(,)?) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
#[rustc_builtin_macro] macro_rules! concat {
|
||||
($($e:ident),+ $(,)?) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
#[rustc_builtin_macro] macro_rules! env {
|
||||
($name:expr $(,)?) => {{ /* compiler built-in */ }};
|
||||
($name:expr, $error_msg:expr $(,)?) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
|
||||
|
||||
@ -749,7 +760,7 @@ fn main() {
|
||||
let res = server.send_request::<HoverRequest>(HoverParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(19, 10),
|
||||
Position::new(30, 10),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
});
|
||||
@ -758,7 +769,7 @@ fn main() {
|
||||
let res = server.send_request::<HoverRequest>(HoverParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(20, 10),
|
||||
Position::new(31, 10),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
});
|
||||
@ -768,23 +779,23 @@ fn main() {
|
||||
GotoDefinitionParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(17, 9),
|
||||
Position::new(28, 9),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
},
|
||||
json!([{
|
||||
"originSelectionRange": {
|
||||
"end": { "character": 10, "line": 17 },
|
||||
"start": { "character": 8, "line": 17 }
|
||||
"end": { "character": 10, "line": 28 },
|
||||
"start": { "character": 8, "line": 28 }
|
||||
},
|
||||
"targetRange": {
|
||||
"end": { "character": 9, "line": 8 },
|
||||
"start": { "character": 0, "line": 7 }
|
||||
"end": { "character": 9, "line": 19 },
|
||||
"start": { "character": 0, "line": 18 }
|
||||
},
|
||||
"targetSelectionRange": {
|
||||
"end": { "character": 8, "line": 8 },
|
||||
"start": { "character": 7, "line": 8 }
|
||||
"end": { "character": 8, "line": 19 },
|
||||
"start": { "character": 7, "line": 19 }
|
||||
},
|
||||
"targetUri": "file:///[..]src/main.rs"
|
||||
}]),
|
||||
@ -794,23 +805,23 @@ fn main() {
|
||||
GotoDefinitionParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(18, 9),
|
||||
Position::new(29, 9),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
},
|
||||
json!([{
|
||||
"originSelectionRange": {
|
||||
"end": { "character": 10, "line": 18 },
|
||||
"start": { "character": 8, "line": 18 }
|
||||
"end": { "character": 10, "line": 29 },
|
||||
"start": { "character": 8, "line": 29 }
|
||||
},
|
||||
"targetRange": {
|
||||
"end": { "character": 9, "line": 12 },
|
||||
"start": { "character": 0, "line":11 }
|
||||
"end": { "character": 9, "line": 23 },
|
||||
"start": { "character": 0, "line": 22 }
|
||||
},
|
||||
"targetSelectionRange": {
|
||||
"end": { "character": 8, "line": 12 },
|
||||
"start": { "character": 7, "line": 12 }
|
||||
"end": { "character": 8, "line": 23 },
|
||||
"start": { "character": 7, "line": 23 }
|
||||
},
|
||||
"targetUri": "file:///[..]src/main.rs"
|
||||
}]),
|
||||
@ -818,8 +829,7 @@ fn main() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
// FIXME: Re-enable once we can run proc-macro tests on rust-lang/rust-analyzer again
|
||||
#[cfg(any())]
|
||||
#[cfg(feature = "sysroot-abi")]
|
||||
fn resolve_proc_macro() {
|
||||
use expect_test::expect;
|
||||
if skip_slow_tests() {
|
||||
@ -837,6 +847,7 @@ fn resolve_proc_macro() {
|
||||
bar = {path = "../bar"}
|
||||
|
||||
//- /foo/src/main.rs
|
||||
#![feature(rustc_attrs, decl_macro)]
|
||||
use bar::Bar;
|
||||
|
||||
#[rustc_builtin_macro]
|
||||
@ -913,7 +924,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
|
||||
let res = server.send_request::<HoverRequest>(HoverParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("foo/src/main.rs"),
|
||||
Position::new(10, 9),
|
||||
Position::new(11, 9),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
});
|
||||
@ -1083,10 +1094,18 @@ pub fn foo() {}
|
||||
|
||||
//- /bar/src/lib.rs
|
||||
pub fn bar() {}
|
||||
|
||||
//- /baz/Cargo.toml
|
||||
[package]
|
||||
name = "baz"
|
||||
version = "0.0.0"
|
||||
|
||||
//- /baz/src/lib.rs
|
||||
"#,
|
||||
)
|
||||
.root("foo")
|
||||
.root("bar")
|
||||
.root("baz")
|
||||
.with_config(json!({
|
||||
"files": {
|
||||
"excludeDirs": ["foo", "bar"]
|
||||
|
@ -37,8 +37,12 @@ pub(crate) fn with_fixture(fixture: &str) -> Project<'_> {
|
||||
"sysroot": null,
|
||||
// Can't use test binary as rustc wrapper.
|
||||
"buildScripts": {
|
||||
"useRustcWrapper": false
|
||||
"useRustcWrapper": false,
|
||||
"enable": false,
|
||||
},
|
||||
},
|
||||
"procMacro": {
|
||||
"enable": false,
|
||||
}
|
||||
}),
|
||||
}
|
||||
@ -251,6 +255,9 @@ pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
|
||||
.clone()
|
||||
.extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
|
||||
.unwrap();
|
||||
if status.health != lsp_ext::Health::Ok {
|
||||
panic!("server errored/warned while loading workspace: {:?}", status.message);
|
||||
}
|
||||
status.quiescent
|
||||
}
|
||||
_ => false,
|
||||
|
Loading…
Reference in New Issue
Block a user