Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Client-side Proc-Macro crate
//!
//! We separate proc-macro expanding logic to an extern program to allow
//! different implementations (e.g. wasm or dylib loading). And this crate
//! is used to provide basic infrastructure for communication between two
//! processes: Client (RA itself), Server (the external program)

pub mod msg;
mod process;
mod rpc;
mod version;

use paths::{AbsPath, AbsPathBuf};
use std::{
    ffi::OsStr,
    io,
    sync::{Arc, Mutex},
};

use tt::{SmolStr, Subtree};

use crate::process::ProcMacroProcessSrv;

pub use rpc::{
    flat::FlatTree, ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind,
};
pub use version::{read_dylib_info, RustCInfo};

#[derive(Debug, Clone)]
pub struct ProcMacroProcessExpander {
    process: Arc<Mutex<ProcMacroProcessSrv>>,
    dylib_path: AbsPathBuf,
    name: SmolStr,
    kind: ProcMacroKind,
}

impl Eq for ProcMacroProcessExpander {}
impl PartialEq for ProcMacroProcessExpander {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
            && self.kind == other.kind
            && self.dylib_path == other.dylib_path
            && Arc::ptr_eq(&self.process, &other.process)
    }
}

impl ProcMacroProcessExpander {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn kind(&self) -> ProcMacroKind {
        self.kind
    }

    pub fn expand(
        &self,
        subtree: &Subtree,
        attr: Option<&Subtree>,
        env: Vec<(String, String)>,
    ) -> Result<Subtree, tt::ExpansionError> {
        let task = ExpansionTask {
            macro_body: FlatTree::new(subtree),
            macro_name: self.name.to_string(),
            attributes: attr.map(FlatTree::new),
            lib: self.dylib_path.to_path_buf().into(),
            env,
        };

        let result: ExpansionResult = self
            .process
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .send_task(msg::Request::ExpansionMacro(task))?;
        Ok(result.expansion.to_subtree())
    }
}

#[derive(Debug)]
pub struct ProcMacroClient {
    /// Currently, the proc macro process expands all procedural macros sequentially.
    ///
    /// That means that concurrent salsa requests may block each other when expanding proc macros,
    /// which is unfortunate, but simple and good enough for the time being.
    ///
    /// Therefore, we just wrap the `ProcMacroProcessSrv` in a mutex here.
    process: Arc<Mutex<ProcMacroProcessSrv>>,
}

impl ProcMacroClient {
    /// Spawns an external process as the proc macro server and returns a client connected to it.
    pub fn extern_process(
        process_path: AbsPathBuf,
        args: impl IntoIterator<Item = impl AsRef<OsStr>>,
    ) -> io::Result<ProcMacroClient> {
        let process = ProcMacroProcessSrv::run(process_path, args)?;
        Ok(ProcMacroClient { process: Arc::new(Mutex::new(process)) })
    }

    pub fn by_dylib_path(&self, dylib_path: &AbsPath) -> Vec<ProcMacroProcessExpander> {
        let _p = profile::span("ProcMacroClient::by_dylib_path");
        match version::read_dylib_info(dylib_path) {
            Ok(info) => {
                if info.version.0 < 1 || info.version.1 < 47 {
                    eprintln!("proc-macro {} built by {:#?} is not supported by Rust Analyzer, please update your rust version.", dylib_path.display(), info);
                }
            }
            Err(err) => {
                eprintln!(
                    "proc-macro {} failed to find the given version. Reason: {}",
                    dylib_path.display(),
                    err
                );
            }
        }

        let macros = match self
            .process
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .find_proc_macros(dylib_path)
        {
            Err(err) => {
                eprintln!("Failed to find proc macros. Error: {:#?}", err);
                return vec![];
            }
            Ok(macros) => macros,
        };

        macros
            .into_iter()
            .map(|(name, kind)| ProcMacroProcessExpander {
                process: self.process.clone(),
                name: name.into(),
                kind,
                dylib_path: dylib_path.to_path_buf(),
            })
            .collect()
    }
}