duchess_macro/
lib.rs

1use argument::{DuchessDeclaration, MethodSelector};
2use parse::Parser;
3use proc_macro::TokenStream;
4use rust_format::Formatter;
5use std::path::PathBuf;
6
7mod argument;
8mod check;
9mod class_info;
10mod codegen;
11mod derive;
12mod java_function;
13mod parse;
14mod reflect;
15mod signature;
16mod substitution;
17mod upcasts;
18
19/// The main duchess macro, used like so
20///
21/// ```rust,ignore
22/// duchess::java_package! {
23///     package some.pkg.name;
24///     class SomeDotId { * }
25/// }
26/// ```
27///
28/// see the tutorial in the [duchess book] for more info.
29///
30/// [duchess book]: https://nikomatsakis.github.io/duchess/
31#[proc_macro]
32pub fn java_package(input: TokenStream) -> TokenStream {
33    let input: proc_macro2::TokenStream = input.into();
34    let decl = match Parser::from(input).parse::<DuchessDeclaration>() {
35        Ok(decl) => decl,
36        Err(err) => return err.to_compile_error().into(),
37    };
38
39    match decl.to_tokens() {
40        Ok(t) => return t.into(),
41        Err(e) => return e.into_compile_error().into(),
42    }
43}
44
45#[proc_macro_attribute]
46pub fn java_function(args: TokenStream, input: TokenStream) -> TokenStream {
47    let args: proc_macro2::TokenStream = args.into();
48    let args = match Parser::from(args).parse::<MethodSelector>() {
49        Ok(decl) => decl,
50        Err(err) => return err.to_compile_error().into(),
51    };
52
53    let item_fn = match syn::parse::<syn::ItemFn>(input) {
54        Ok(item_fn) => item_fn,
55        Err(err) => return err.into_compile_error().into(),
56    };
57
58    match java_function::java_function(args, item_fn) {
59        Ok(t) => t.into(),
60        Err(err) => err.into_compile_error().into(),
61    }
62}
63
64synstructure::decl_derive!([ToRust, attributes(java)] => derive::derive_to_rust);
65
66synstructure::decl_derive!([ToJava, attributes(java)] => derive::derive_to_java);
67
68lazy_static::lazy_static! {
69    static ref DEBUG_DIR: PathBuf = {
70        let tmp_dir = tempfile::TempDir::new().expect("failed to create temp directory");
71        tmp_dir.into_path()
72    };
73}
74
75fn debug_tokens(name: impl std::fmt::Display, token_stream: &proc_macro2::TokenStream) {
76    let Ok(debug_filter) = std::env::var("DUCHESS_DEBUG") else {
77        return;
78    };
79    let name = name.to_string();
80    let debug_enabled = match debug_filter {
81        f if f.eq_ignore_ascii_case("true") || f.eq_ignore_ascii_case("1") => true,
82        filter => name.starts_with(&filter)
83    };
84    if debug_enabled {
85        let path = DEBUG_DIR.join(name.replace('.', "_")).with_extension("rs");
86        match rust_format::RustFmt::default().format_tokens(token_stream.clone()) {
87            Ok(formatted_tokens) => {
88                std::fs::write(&path, formatted_tokens).expect("failed to write to debug file");
89            }
90            Err(_) => {
91                std::fs::write(&path, format!("{token_stream:?}")).expect("failed to write to debug file");
92            }
93        }
94        // in JetBrains terminal, links are only clickable with a `file:///` prefix. But in VsCode
95        // iTerm, and most other terminals, they are only clickable if they are absolute paths.
96        if running_in_jetbrains() {
97            eprintln!("file:///{}", path.display())
98        } else {
99            eprintln!("{}", path.display())
100        }
101    }
102}
103
104fn running_in_jetbrains() -> bool {
105    std::env::var("TERMINAL_EMULATOR").map(|var|var.contains("JetBrains")).unwrap_or_default()
106}