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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! # FFI bindings to Node-API symbols
//!
//! Rust types generated from [Node-API](https://nodejs.org/api/n-api.html).

// These types are manually copied from bindings generated from `bindgen`. To
// update, use the following approach:
//
// * Run a debug build of Neon at least once to install `nodejs-sys`
// * Open the generated bindings at `target/debug/build/nodejs-sys-*/out/bindings.rs`
// * Copy the types needed into `types.rs` and `functions.rs`
// * Modify to match Rust naming conventions:
//   - Remove `napi_` prefixes
//   - Use `PascalCase` for types
//   - Rename types that match a reserved word

/// Constructs the name of a N-API symbol as a string from a function identifier
/// E.g., `get_undefined` becomes `"napi_get_undefined"`
macro_rules! napi_name {
    // Explicitly replace identifiers that have been renamed from the N-API
    // symbol because they would match a reserved word.
    (typeof_value) => {
        "napi_typeof"
    };
    // Default case: Stringify the identifier and prefix with `napi_`
    ($name:ident) => {
        concat!("napi_", stringify!($name))
    };
}

/// Generate dynamic bindings to N-API symbols from definitions in an
/// block `extern "C"`.
///
/// * A single global mutable struct holds references to the N-API functions
/// * The global `Napi` struct  is initialized with stubs that panic if called
/// * A `load` function is generated that loads the N-API symbols from the
///   host process and replaces the global struct with real implementations
/// * `load` should be called exactly once before using any N-API functions
/// * Wrapper functions are generated to delegate to fields in the `Napi` struct
///
/// Sample input:
///
/// ```ignore
/// extern "C" {
///     fn get_undefined(env: Env, result: *mut Value) -> Status;
///     /* Additional functions may be included */  
/// }
/// ```
///
/// Generated output:
///
/// ```ignore
/// // Each field is a pointer to a N-API function
/// struct Napi {
///     get_undefined: unsafe extern "C" fn(env: Env, result: *mut Value) -> Status,
///     /* ... repeat for each N-API function */
/// }
///
/// // Defines a panic function that is called if symbols have not been loaded
/// #[inline(never)]
/// fn panic_load<T>() -> T {
///     panic!("Must load N-API bindings")
/// }
///
/// // Mutable global instance of the Napi struct
/// // Initialized with stubs of N-API methods that panic
/// static mut NAPI: Napi = {
///     // Stubs are defined in a block to prevent naming conflicts with wrappers
///     unsafe extern "C" fn get_undefined(_: Env, _: *mut Value) -> Status {
///         panic_load()
///     }
///     /* ... repeat for each N-API function */
///
///     Napi {
///         get_undefined,
///         /* ... repeat for each N-API function */
///     }
/// };
///
/// // Load N-API symbols from the host process
/// // # Safety: Must only be called once
/// pub(super) unsafe fn load(
///     host: &libloading::Library,
///     actual_napi_version: u32,
///     expected_napi_version: u32,
/// ) -> Result<(), libloading::Error> {
///     assert!(
///         actual_napi_version >= expected_napi_version,
///         "Minimum required N-API version {}, found {}.",
///         expected_napi_version,
///         actual_napi_version,
///     );
///
///     NAPI = Napi {
///         // Load each N-API symbol
///         get_undefined: *host.get("napi_get_undefined".as_bytes())?,
///         /* ... repeat for each N-API function */
///     };
///
///     Ok(())
/// }
///
/// // Each N-API function has wrapper for easy calling. These calls are optimized
/// // to a single pointer dereference.
/// #[inline]
/// pub(crate) unsafe fn get_undefined(env: Env, result: *mut Value) -> Status {
///     (NAPI.get_undefined)(env, result)
/// }
/// ```
macro_rules! generate {
    (#[$extern_attr:meta] extern "C" {
        $($(#[$attr:meta])? fn $name:ident($($param:ident: $ptype:ty$(,)?)*)$( -> $rtype:ty)?;)+
    }) => {
        struct Napi {
            $(
                $name: unsafe extern "C" fn(
                    $($param: $ptype,)*
                )$( -> $rtype)*,
            )*
        }

        #[inline(never)]
        fn panic_load<T>() -> T {
            panic!("Node-API symbol has not been loaded")
        }

        static mut NAPI: Napi = {
            $(
                unsafe extern "C" fn $name($(_: $ptype,)*)$( -> $rtype)* {
                    panic_load()
                }
            )*

            Napi {
                $(
                    $name,
                )*
            }
        };

        pub(super) unsafe fn load(host: &libloading::Library) {
            let print_warn = |err| eprintln!("WARN: {}", err);

            NAPI = Napi {
                $(
                    $name: match host.get(napi_name!($name).as_bytes()) {
                        Ok(f) => *f,
                        // Node compatible runtimes may not have full coverage of Node-API
                        // (e.g., bun). Instead of failing to start, warn on start and
                        // panic when the API is called.
                        // https://github.com/Jarred-Sumner/bun/issues/158
                        Err(err) => {
                            print_warn(err);
                            NAPI.$name
                        },
                    },
                )*
            };
        }

        $(
            #[$extern_attr] $(#[$attr])? #[inline]
            #[doc = concat!(
                "[`",
                napi_name!($name),
                "`](https://nodejs.org/api/n-api.html#",
                napi_name!($name),
                ")",
            )]
            pub unsafe fn $name($($param: $ptype,)*)$( -> $rtype)* {
                (NAPI.$name)($($param,)*)
            }
        )*
    };
}

pub use self::{functions::*, types::*};

mod functions;
mod types;