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
use std::iter;

use syn::{
    parse_quote, FnArg, Pat, PatIdent, PatType, Path, PathArguments, Signature, Type,
    TypeReference,
};
use proc_macro_error::emit_error;

pub fn canonicalize_path(path: &Path) -> Path {
    let mut result = path.clone();
    result.segments = result
        .segments
        .into_iter()
        .map(|mut seg| {
            seg.arguments = PathArguments::None;
            seg
        })
        .collect();

    result
}

pub fn is_self_method(signature: &Signature) -> bool {
    signature.inputs.iter().any(|i| match i {
        FnArg::Receiver(_) => true,
        FnArg::Typed(t) => match &*t.pat {
            Pat::Ident(PatIdent { ident, .. }) => ident == "self",
            _ => false,
        },
    })
}

pub fn get_env_arg(signature: Signature) -> (Signature, Option<FnArg>) {
    let self_method = is_self_method(&signature);

    // Check whether second argument (first exluding self) is of type &JNIEnv, if so we take it out from the signature
    let possible_env_arg = if !self_method {
        signature.inputs.iter().next()
    } else {
        signature.inputs.iter().nth(1)
    };

    let has_explicit_env_arg = if let Some(FnArg::Typed(PatType { ty, .. })) = possible_env_arg {
        if let Type::Reference(TypeReference { elem, .. }) = &**ty {
            if let Type::Path(t) = &**elem {
                let full_path: Path = parse_quote! { ::robusta_jni::jni::JNIEnv };
                let imported_path: Path = parse_quote! { JNIEnv };
                let canonicalized_type_path = canonicalize_path(&t.path);

                canonicalized_type_path == imported_path || canonicalized_type_path == full_path
            } else {
                false
            }
        } else if let Type::Path(t) = &**ty {
            /* If the user has input `env: JNIEnv` instead of `env: &JNIEnv`, we let her know. */
            let full_path: Path = parse_quote! { ::robusta_jni::jni::JNIEnv };
            let imported_path: Path = parse_quote! { JNIEnv };
            let canonicalized_type_path = canonicalize_path(&t.path);

            if canonicalized_type_path == imported_path || canonicalized_type_path == full_path {
                emit_error!(t, "explicit environment parameter must be of type `&JNIEnv`");
            }

            false
        } else {
            false
        }
    } else {
        false
    };

    let (transformed_signature, env_arg): (Signature, Option<FnArg>) = if has_explicit_env_arg {
        let mut inner_signature = signature;

        let mut iter = inner_signature.inputs.into_iter();

        if self_method {
            let self_arg = iter.next();
            let env_arg = iter.next();

            inner_signature.inputs = iter::once(self_arg.unwrap()).chain(iter).collect();
            (inner_signature, env_arg)
        } else {
            let env_arg = iter.next();
            inner_signature.inputs = iter.collect();

            (inner_signature, env_arg)
        }
    } else {
        (signature, None)
    };

    (transformed_signature, env_arg)
}

pub fn get_abi(sig: &Signature) -> Option<String> {
    sig
        .abi
        .as_ref()
        .and_then(|l| l.name.as_ref().map(|n| n.value()))
}