rtk_lua/
api.rs

1use anyhow::Context;
2use mlua::{Either, FromLua, IntoLua, Lua};
3
4use crate::{
5    ext::TableSetFnExt, impl_enum_into_lua, impl_into_lua, versioning::RtkRustcDriverVersion,
6};
7
8pub trait RtkLuaScriptExecutor: Send + Sync + Clone + 'static {
9    /// Intake the driver version provided by the script
10    fn intake_version(&self, version: RtkRustcDriverVersion);
11
12    /// Intake the driver version provided by the script to use when debug assertions are enabled
13    fn intake_debug_version(&self, version: RtkRustcDriverVersion) {
14        self.intake_version(version);
15    }
16
17    fn query_method_calls(&self, query: MethodCallQuery) -> Vec<MethodCall>;
18    fn query_trait_impls(&self, query: Location) -> Vec<TraitImpl>;
19    fn query_functions(&self, query: Location) -> Vec<FunctionTypeValue>;
20    fn query_function_calls(&self, query: Location) -> Vec<FunctionCall>;
21
22    fn log_note(&self, msg: String);
23    fn log_warn(&self, msg: String);
24    fn log_error(&self, msg: String);
25    fn log_fatal_error(&self, msg: String) -> !;
26
27    fn emit(&self, text: String);
28}
29
30/// Injects the full API into the table
31pub fn inject(
32    lua: &Lua,
33    table: &mlua::Table,
34    exec: impl RtkLuaScriptExecutor,
35) -> anyhow::Result<()> {
36    let intake_version_exec = exec.clone();
37
38    table
39        .set_rtk_api_fn(lua, "version", move |version: RtkRustcDriverVersion| {
40            intake_version_exec.intake_version(version);
41            mlua::Nil
42        })
43        .context("failed to set intake_version function")?;
44
45    let intake_debug_version_exec = exec.clone();
46    table
47        .set_rtk_api_fn(lua, "dbg_version", move |version: RtkRustcDriverVersion| {
48            intake_debug_version_exec.intake_debug_version(version);
49            mlua::Nil
50        })
51        .context("failed to set intake_debug_version function")?;
52
53    let note_exec = exec.clone();
54    table
55        .set_rtk_api_fn(lua, "note", move |msg: String| {
56            note_exec.log_note(msg);
57            mlua::Nil
58        })
59        .context("failed to set debug function")?;
60
61    let warn_exec = exec.clone();
62    table
63        .set_rtk_api_fn(lua, "warn", move |msg: String| {
64            warn_exec.log_warn(msg);
65            mlua::Nil
66        })
67        .context("failed to set warn function")?;
68
69    let error_exec = exec.clone();
70    table
71        .set_rtk_api_fn(lua, "error", move |msg: String| {
72            error_exec.log_error(msg);
73            mlua::Nil
74        })
75        .context("failed to set error function")?;
76
77    let fatal_error_exec = exec.clone();
78    table
79        .set_rtk_api_fn(lua, "fatal_error", move |msg: String| {
80            fatal_error_exec.log_fatal_error(msg);
81            // required or else we get annoying warnings about the return type
82            #[allow(unreachable_code)]
83            mlua::Nil
84        })
85        .context("failed to set fatal_error function")?;
86
87    let query_method_calls_exec = exec.clone();
88    table
89        .set_rtk_api_fn(lua, "query_method_calls", move |query: MethodCallQuery| {
90            query_method_calls_exec.query_method_calls(query)
91        })
92        .context("failed to set query_method_calls function")?;
93
94    let query_trait_impls_exec = exec.clone();
95    table
96        .set_rtk_api_fn(lua, "query_trait_impls", move |query: Location| {
97            query_trait_impls_exec.query_trait_impls(query)
98        })
99        .context("failed to set query_trait_impls function")?;
100
101    let query_functions_exec = exec.clone();
102    table
103        .set_rtk_api_fn(lua, "query_functions", move |query: Location| {
104            query_functions_exec.query_functions(query)
105        })
106        .context("failed to set query_functions function")?;
107
108    let query_function_calls_exec = exec.clone();
109    table
110        .set_rtk_api_fn(lua, "query_function_calls", move |query: Location| {
111            query_function_calls_exec.query_function_calls(query)
112        })
113        .context("failed to set query_function_calls function")?;
114
115    let emit_exec = exec.clone();
116    table
117        .set_rtk_api_fn(lua, "emit", move |text: String| {
118            emit_exec.emit(text);
119            mlua::Nil
120        })
121        .context("failed to set emit function")?;
122
123    Ok(())
124}
125
126#[derive(Clone, Debug, PartialEq, Eq, Default)]
127pub struct Location {
128    pub crate_name: String,
129    pub path: Vec<String>,
130    pub impl_block_number: Option<usize>,
131}
132
133impl FromLua for Location {
134    fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
135        let table = value
136            .as_table()
137            .ok_or_else(|| mlua::Error::FromLuaConversionError {
138                from: "Value",
139                to: "Location".to_string(),
140                message: Some("expected a table".to_string()),
141            })?;
142
143        let crate_name: String = table.get("crate_name")?;
144        let path: Vec<String> = table.get("path")?;
145        let impl_block_number: Option<usize> = table.get("impl_block_number")?;
146
147        Ok(Location {
148            crate_name,
149            path,
150            impl_block_number,
151        })
152    }
153}
154
155impl_into_lua! {
156    Location {
157        crate_name,
158        path,
159        impl_block_number,
160    }
161}
162
163/// A query for method calls matching a specific path.
164/// This can be used, for example, to look for axum routes
165#[derive(Clone, Debug, Eq, PartialEq)]
166pub struct MethodCallQuery {
167    /// If specified, this requires this method call to originate from a prior parent method call.
168    /// For instance with the given source:
169    /// ```rust,ignore
170    /// my_var.globals().set("something", 1).set("something_else", 2);
171    /// ```
172    /// By setting `parent` to the method call query of `globals`, we can enforce that the
173    /// set call is in a chain of `globals` and not a set on some other table.
174    pub parent: Option<Box<MethodCallQuery>>,
175    /// The path to the module this method call sits in.
176    pub location: Location,
177}
178
179impl_into_lua! {
180    MethodCallQuery {
181        parent => parent.map(|b| *b),
182        location,
183    }
184}
185
186impl FromLua for MethodCallQuery {
187    fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> mlua::Result<Self> {
188        let table = value
189            .as_table()
190            .ok_or_else(|| mlua::Error::FromLuaConversionError {
191                from: "Value",
192                to: "MethodCallQuery".to_string(),
193                message: Some("expected a table".to_string()),
194            })?;
195
196        let parent = match table.get("parent")? {
197            mlua::Nil => None,
198            t => Some(Box::new(Self::from_lua(t, _lua)?)),
199        };
200
201        let location: Location =
202            table
203                .get("location")
204                .map_err(|_| mlua::Error::FromLuaConversionError {
205                    from: "Value",
206                    to: "Location".to_string(),
207                    message: Some("expected a Location".to_string()),
208                })?;
209
210        Ok(MethodCallQuery { parent, location })
211    }
212}
213
214#[derive(Clone, Debug)]
215pub struct MethodCall {
216    /// The query that produced this method call. This won't always be your own query, as certain
217    /// situations will cause one to be automatically generated. For instance, if you make a method
218    /// call query one of the arguments to it can be another method call.
219    pub origin: MethodCallQuery,
220    pub args: Vec<Value>,
221    pub in_item_id: String,
222}
223
224impl_into_lua! {
225    MethodCall {
226        origin,
227        args,
228        in_item_id,
229    }
230}
231
232#[derive(Clone, Debug)]
233pub enum Value {
234    StringLiteral(String),
235    IntegerLiteral(i64),
236    FloatLiteral(f64),
237
238    FunctionCall(FunctionCall),
239    MethodCall(MethodCall),
240
241    Type(TypeValue),
242}
243
244impl_enum_into_lua! {
245    Value {
246        StringLiteral(s) => s,
247        IntegerLiteral(i) => i,
248        FloatLiteral(f) => f,
249
250        FunctionCall(f) => f,
251        MethodCall(m) => m,
252
253        Type(t) => t,
254    }
255}
256
257#[derive(Clone, Debug)]
258pub enum TypeValue {
259    String,
260
261    U8,
262    U16,
263    U32,
264    U64,
265    U128,
266    Usize,
267
268    I8,
269    I16,
270    I32,
271    I64,
272    I128,
273    Isize,
274
275    F32,
276    F64,
277
278    Bool,
279
280    HashMap(Box<TypeValue>, Box<TypeValue>),
281    Vec(Box<TypeValue>),
282    Result(Box<TypeValue>, Box<TypeValue>),
283
284    Struct(StructTypeValue),
285    Enum(EnumTypeValue),
286
287    Closure(ClosureTypeValue),
288    Function(FunctionTypeValue),
289
290    Option(Box<TypeValue>),
291
292    Tuple(Vec<TypeValue>),
293
294    RecursiveRef(Location),
295}
296
297impl_enum_into_lua! {
298    TypeValue {
299        String,
300        U8,
301        U16,
302        U32,
303        U64,
304        U128,
305        Usize,
306        I8,
307        I16,
308        I32,
309        I64,
310        I128,
311        Isize,
312        F32,
313        F64,
314        Bool,
315
316        // HashMap(k, v) => (*k, *v),
317        HashMap(_, _) => mlua::Nil,
318        Vec(t) => *t,
319        // Result(ok, err) => (*ok, *err),
320        Result(_, _) => mlua::Nil,
321
322        Struct(s) => s,
323        Enum(e) => e,
324
325        Closure(c) => c,
326
327        Function(f) => f,
328
329        Option(t) => *t,
330
331        Tuple(elements) => elements,
332
333        RecursiveRef(location) => location,
334    }
335}
336
337#[derive(Clone, Debug)]
338pub struct StructTypeValue {
339    pub location: Location,
340    pub fields: Vec<StructTypeValueField>,
341    pub doc_comment: Option<String>,
342    pub attributes: Vec<Attribute>,
343}
344
345impl_into_lua! {
346    StructTypeValue {
347        location,
348        fields,
349        doc_comment,
350        attributes,
351    }
352}
353
354#[derive(Clone, Debug)]
355pub struct StructTypeValueField {
356    pub name: Either<usize, String>,
357    pub doc_comment: Option<String>,
358    pub attributes: Vec<Attribute>,
359    pub value: TypeValue,
360}
361
362impl_into_lua! {
363    StructTypeValueField {
364        name,
365        doc_comment,
366        attributes,
367        value,
368    }
369}
370
371#[derive(Clone, Debug)]
372pub struct EnumTypeValue {
373    pub location: Location,
374    pub variants: Vec<EnumTypeValueVariant>,
375    pub doc_comment: Option<String>,
376    pub attributes: Vec<Attribute>,
377}
378
379impl_into_lua! {
380    EnumTypeValue {
381        location,
382        variants,
383        doc_comment,
384        attributes,
385    }
386}
387
388#[derive(Clone, Debug)]
389pub struct EnumTypeValueVariant {
390    pub name: String,
391    /// If this variant has a value, this will be the type of that value otherwise its just a unit
392    /// variant
393    pub value: Option<TypeValue>,
394    pub doc_comment: Option<String>,
395    pub attributes: Vec<Attribute>,
396}
397
398impl_into_lua! {
399    EnumTypeValueVariant {
400        name,
401        value,
402        doc_comment,
403        attributes,
404    }
405}
406
407/// A closure definition itself. The args are just a struct ultimately
408#[derive(Clone, Debug)]
409pub struct ClosureTypeValue {
410    pub args: Vec<TypeValue>,
411    pub return_type: Option<Box<TypeValue>>,
412}
413
414impl_into_lua! {
415    ClosureTypeValue {
416        args,
417        return_type => return_type.map(|b| *b),
418    }
419}
420
421#[derive(Clone, Debug)]
422pub struct FunctionTypeValue {
423    pub location: Location,
424    pub args_struct: StructTypeValue,
425    pub return_type: Option<Box<TypeValue>>,
426    pub item_id: String,
427    pub attributes: Vec<Attribute>,
428    pub doc_comment: Option<String>,
429    pub is_async: bool,
430}
431
432impl_into_lua! {
433    FunctionTypeValue {
434        location,
435        args_struct,
436        return_type => return_type.map(|b| *b),
437        item_id,
438        attributes,
439        doc_comment,
440        is_async,
441    }
442}
443
444/// An attribute in the source code.
445#[derive(Clone, Debug)]
446pub struct Attribute {
447    pub name: String,
448    // in the case of a rename, this will be `"my_name"` _NOT_ `my_name`
449    pub value_str: Option<String>,
450}
451
452impl_into_lua! {
453    Attribute {
454        name,
455        value_str,
456    }
457}
458
459#[derive(Clone, Debug)]
460pub struct FunctionCall {
461    pub location: Location,
462    pub args: Vec<Value>,
463    pub in_item_id: String,
464}
465
466impl_into_lua! {
467    FunctionCall {
468        location,
469        args,
470        in_item_id,
471    }
472}
473
474#[derive(Clone, Debug)]
475pub struct TraitImpl {
476    pub trait_location: Location,
477    pub for_type: TypeValue,
478    pub functions: Vec<FunctionTypeValue>,
479}
480
481impl_into_lua! {
482    TraitImpl {
483        trait_location,
484        for_type,
485        functions,
486    }
487}