ext_php_rs_derive/
lib.rs

1//! Macros for the `php-ext` crate.
2mod class;
3mod constant;
4mod enum_;
5mod extern_;
6mod fastcall;
7mod function;
8mod helpers;
9mod impl_;
10mod interface;
11mod module;
12mod parsing;
13mod syn_ext;
14mod zval;
15
16use proc_macro::TokenStream;
17use proc_macro2::TokenStream as TokenStream2;
18use syn::{
19    DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, ItemTrait,
20};
21
22extern crate proc_macro;
23
24// BEGIN DOCS FROM classes.md
25/// # `#[php_class]` Attribute
26///
27/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
28/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
29/// well as registering the class to be registered with the `#[php_module]`
30/// macro.
31///
32/// ## Options
33///
34/// There are additional macros that modify the class. These macros **must** be
35/// placed underneath the `#[php_class]` attribute.
36///
37/// - `name` - Changes the name of the class when exported to PHP. The Rust
38///   struct name is kept the same. If no name is given, the name of the struct
39///   is used. Useful for namespacing classes.
40/// - `change_case` - Changes the case of the class name when exported to PHP.
41/// - `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - Sets the parent
42///   class of the class. Can only be used once. `ce_fn` must be a function with
43///   the signature `fn() -> &'static ClassEntry`.
44/// - `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the
45///   given interface on the class. Can be used multiple times. `ce_fn` must be
46///   a valid function with the signature `fn() -> &'static ClassEntry`.
47///
48/// You may also use the `#[php(prop)]` attribute on a struct field to use the
49/// field as a PHP property. By default, the field will be accessible from PHP
50/// publicly with the same name as the field. Property types must implement
51/// `IntoZval` and `FromZval`.
52///
53/// You can rename the property with options:
54///
55/// - `name` - Allows you to rename the property, e.g. `#[php(name =
56///   "new_name")]`
57/// - `change_case` - Allows you to rename the property using rename rules, e.g.
58///   `#[php(change_case = PascalCase)]`
59///
60/// ## Restrictions
61///
62/// ### No lifetime parameters
63///
64/// Rust lifetimes are used by the Rust compiler to reason about a program's
65/// memory safety. They are a compile-time only concept;
66/// there is no way to access Rust lifetimes at runtime from a dynamic language
67/// like PHP.
68///
69/// As soon as Rust data is exposed to PHP,
70/// there is no guarantee which the Rust compiler can make on how long the data
71/// will live. PHP is a reference-counted language and those references can be
72/// held for an arbitrarily long time, which is untraceable by the Rust
73/// compiler. The only possible way to express this correctly is to require that
74/// any `#[php_class]` does not borrow data for any lifetime shorter than the
75/// `'static` lifetime, i.e. the `#[php_class]` cannot have any lifetime
76/// parameters.
77///
78/// When you need to share ownership of data between PHP and Rust,
79/// instead of using borrowed references with lifetimes, consider using
80/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
81///
82/// ### No generic parameters
83///
84/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled
85/// implementations each time it is used with a different concrete type for `T`.
86/// These new implementations are generated by the compiler at each usage site.
87/// This is incompatible with wrapping `Foo` in PHP,
88/// where there needs to be a single compiled implementation of `Foo` which is
89/// integrated with the PHP interpreter.
90///
91/// ## Example
92///
93/// This example creates a PHP class `Human`, adding a PHP property `address`.
94///
95/// ```rust,no_run,ignore
96/// # #![cfg_attr(windows, feature(abi_vectorcall))]
97/// # extern crate ext_php_rs;
98/// use ext_php_rs::prelude::*;
99///
100/// #[php_class]
101/// pub struct Human {
102///     name: String,
103///     age: i32,
104///     #[php(prop)]
105///     address: String,
106/// }
107///
108/// #[php_module]
109/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
110///     module.class::<Human>()
111/// }
112/// # fn main() {}
113/// ```
114///
115/// Create a custom exception `RedisException`, which extends `Exception`, and
116/// put it in the `Redis\Exception` namespace:
117///
118/// ```rust,no_run,ignore
119/// # #![cfg_attr(windows, feature(abi_vectorcall))]
120/// # extern crate ext_php_rs;
121/// use ext_php_rs::{
122///     prelude::*,
123///     exception::PhpException,
124///     zend::ce
125/// };
126///
127/// #[php_class]
128/// #[php(name = "Redis\\Exception\\RedisException")]
129/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
130/// #[derive(Default)]
131/// pub struct RedisException;
132///
133/// // Throw our newly created exception
134/// #[php_function]
135/// pub fn throw_exception() -> PhpResult<i32> {
136///     Err(PhpException::from_class::<RedisException>("Not good!".into()))
137/// }
138///
139/// #[php_module]
140/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
141///     module
142///         .class::<RedisException>()
143///         .function(wrap_function!(throw_exception))
144/// }
145/// # fn main() {}
146/// ```
147///
148/// ## Implementing an Interface
149///
150/// To implement an interface, use `#[php(implements(ce = ce_fn, stub =
151/// "InterfaceName")]` where `ce_fn` is an function returning a `ClassEntry`. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
152///
153/// ````rust,no_run,ignore
154/// # #![cfg_attr(windows, feature(abi_vectorcall))]
155/// # extern crate ext_php_rs;
156/// use ext_php_rs::{
157///     prelude::*,
158///     exception::PhpResult,
159///     types::Zval,
160///     zend::ce,
161/// };
162///
163/// #[php_class]
164/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
165/// #[derive(Default)]
166/// pub struct EvenNumbersArray;
167///
168/// /// Returns `true` if the array offset is an even number.
169/// /// Usage:
170/// /// ```php
171/// /// $arr = new EvenNumbersArray();
172/// /// var_dump($arr[0]); // true
173/// /// var_dump($arr[1]); // false
174/// /// var_dump($arr[2]); // true
175/// /// var_dump($arr[3]); // false
176/// /// var_dump($arr[4]); // true
177/// /// var_dump($arr[5] = true); // Fatal error:  Uncaught Exception: Setting values is not supported
178/// /// ```
179/// #[php_impl]
180/// impl EvenNumbersArray {
181///     pub fn __construct() -> EvenNumbersArray {
182///         EvenNumbersArray {}
183///     }
184///     // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
185///     pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
186///         offset.is_long()
187///     }
188///     pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
189///         let integer_offset = offset.long().ok_or("Expected integer offset")?;
190///         Ok(integer_offset % 2 == 0)
191///     }
192///     pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
193///         Err("Setting values is not supported".into())
194///     }
195///     pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
196///         Err("Setting values is not supported".into())
197///     }
198/// }
199///
200/// #[php_module]
201/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
202///     module.class::<EvenNumbersArray>()
203/// }
204/// # fn main() {}
205/// ````
206// END DOCS FROM classes.md
207#[proc_macro_attribute]
208pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
209    php_class_internal(args.into(), input.into()).into()
210}
211
212#[allow(clippy::needless_pass_by_value)]
213fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
214    let input = parse_macro_input2!(input as ItemStruct);
215    if !args.is_empty() {
216        return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
217    }
218
219    class::parser(input).unwrap_or_else(|e| e.to_compile_error())
220}
221
222// BEGIN DOCS FROM enum.md
223/// # `#[php_enum]` Attribute
224///
225/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute
226/// macro. This attribute derives the `RegisteredClass` and `PhpEnum` traits on
227/// your enum. To register the enum use the `enumeration::<EnumName>()` method
228/// on the `ModuleBuilder` in the `#[php_module]` macro.
229///
230/// ## Options
231///
232/// The `#[php_enum]` attribute can be configured with the following options:
233/// - `#[php(name = "EnumName")]` or `#[php(change_case = snake_case)]`: Sets
234///   the name of the enum in PHP. The default is the `PascalCase` name of the
235///   enum.
236/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust
237///   discriminants (e.g., `Hearts = 1`).
238///
239/// The cases of the enum can be configured with the following options:
240/// - `#[php(name = "CaseName")]` or `#[php(change_case = snake_case)]`: Sets
241///   the name of the enum case in PHP. The default is the `PascalCase` name of
242///   the case.
243/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant
244///   value for the enum case. This can be a string or an integer. If not set,
245///   the case will be exported as a simple enum case without a discriminant.
246///
247/// ### Example
248///
249/// This example creates a PHP enum `Suit`.
250///
251/// ```rust,no_run,ignore
252/// # #![cfg_attr(windows, feature(abi_vectorcall))]
253/// # extern crate ext_php_rs;
254/// use ext_php_rs::prelude::*;
255///
256/// #[php_enum]
257/// pub enum Suit {
258///     Hearts,
259///     Diamonds,
260///     Clubs,
261///     Spades,
262/// }
263///
264/// #[php_module]
265/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
266///     module.enumeration::<Suit>()
267/// }
268/// # fn main() {}
269/// ```
270///
271/// ## Backed Enums
272/// Enums can also be backed by either `i64` or `&'static str`. Those values can
273/// be set using the `#[php(value = "value")]` or `#[php(value = 123)]`
274/// attributes on the enum variants.
275///
276/// All variants must have a value of the same type, either all `i64` or all
277/// `&'static str`.
278///
279/// ```rust,no_run,ignore
280/// # #![cfg_attr(windows, feature(abi_vectorcall))]
281/// # extern crate ext_php_rs;
282/// use ext_php_rs::prelude::*;
283///
284/// #[php_enum]
285/// pub enum Suit {
286///     #[php(value = "hearts")]
287///     Hearts,
288///     #[php(value = "diamonds")]
289///     Diamonds,
290///     #[php(value = "clubs")]
291///     Clubs,
292///     #[php(value = "spades")]
293///     Spades,
294/// }
295/// #[php_module]
296/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
297///     module.enumeration::<Suit>()
298/// }
299/// # fn main() {}
300/// ```
301///
302/// ### 'Native' Discriminators
303/// Native rust discriminants are currently not supported and will not be
304/// exported to PHP.
305///
306/// To avoid confusion a compiler error will be raised if you try to use a
307/// native discriminant. You can ignore this error by adding the
308/// `#[php(allow_native_discriminants)]` attribute to your enum.
309///
310/// ```rust,no_run,ignore
311/// # #![cfg_attr(windows, feature(abi_vectorcall))]
312/// # extern crate ext_php_rs;
313/// use ext_php_rs::prelude::*;
314///
315/// #[php_enum]
316/// #[php(allow_native_discriminants)]
317/// pub enum Suit {
318///     Hearts = 1,
319///     Diamonds = 2,
320///     Clubs = 3,
321///     Spades = 4,
322/// }
323///
324/// #[php_module]
325/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
326///     module.enumeration::<Suit>()
327/// }
328/// # fn main() {}
329/// ```
330///
331///
332/// TODO: Add backed enums example
333// END DOCS FROM enum.md
334#[proc_macro_attribute]
335pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
336    php_enum_internal(args.into(), input.into()).into()
337}
338
339fn php_enum_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
340    let input = parse_macro_input2!(input as ItemEnum);
341
342    enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
343}
344
345// BEGIN DOCS FROM interface.md
346/// # `#[php_interface]` Attribute
347///
348/// You can export a `Trait` block to PHP. This exports all methods as well as
349/// constants to PHP on the interface. Trait method SHOULD NOT contain default
350/// implementations, as these are not supported in PHP interfaces.
351///
352/// ## Options
353///
354/// By default all constants are renamed to `UPPER_CASE` and all methods are
355/// renamed to `camelCase`. This can be changed by passing the
356/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
357/// the `impl` block. The options are:
358///
359/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
360///   case.
361/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
362///   snake case.
363///
364/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
365/// for a list of all available cases.
366///
367/// ## Methods
368///
369/// See the [`php_impl`](./impl.md#)
370///
371/// ## Constants
372///
373/// See the [`php_impl`](./impl.md#)
374///
375/// ## Example
376///
377/// Define an example trait with methods and constant:
378///
379/// ```rust,no_run,ignore
380/// # #![cfg_attr(windows, feature(abi_vectorcall))]
381/// # extern crate ext_php_rs;
382/// use ext_php_rs::{prelude::*, types::ZendClassObject};
383///
384///
385/// #[php_interface]
386/// #[php(name = "Rust\\TestInterface")]
387/// trait Test {
388///     const TEST: &'static str = "TEST";
389///
390///     fn co();
391///
392///     #[php(defaults(value = 0))]
393///     fn set_value(&mut self, value: i32);
394/// }
395///
396/// #[php_module]
397/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
398///     module
399///         .interface::<PhpInterfaceTest>()
400/// }
401///
402/// # fn main() {}
403/// ```
404///
405/// Using our newly created interface in PHP:
406///
407/// ```php
408/// <?php
409///
410/// assert(interface_exists("Rust\TestInterface"));
411///
412/// class B implements Rust\TestInterface {
413///
414///     public static function co() {}
415///
416///     public function setValue(?int $value = 0) {
417///
418///     }
419/// }
420/// ```
421// END DOCS FROM interface.md
422#[proc_macro_attribute]
423pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
424    php_interface_internal(args.into(), input.into()).into()
425}
426
427fn php_interface_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
428    let input = parse_macro_input2!(input as ItemTrait);
429
430    interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
431}
432
433// BEGIN DOCS FROM function.md
434/// # `#[php_function]` Attribute
435///
436/// Used to annotate functions which should be exported to PHP. Note that this
437/// should not be used on class methods - see the `#[php_impl]` macro for that.
438///
439/// See the [list of types](../types/index.md) that are valid as parameter and
440/// return types.
441///
442/// ## Optional parameters
443///
444/// Optional parameters can be used by setting the Rust parameter type to a
445/// variant of `Option<T>`. The macro will then figure out which parameters are
446/// optional by using the last consecutive arguments that are a variant of
447/// `Option<T>` or have a default value.
448///
449/// ```rust,no_run,ignore
450/// # #![cfg_attr(windows, feature(abi_vectorcall))]
451/// # extern crate ext_php_rs;
452/// use ext_php_rs::prelude::*;
453///
454/// #[php_function]
455/// pub fn greet(name: String, age: Option<i32>) -> String {
456///     let mut greeting = format!("Hello, {}!", name);
457///
458///     if let Some(age) = age {
459///         greeting += &format!(" You are {} years old.", age);
460///     }
461///
462///     greeting
463/// }
464///
465/// #[php_module]
466/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
467///     module.function(wrap_function!(greet))
468/// }
469/// # fn main() {}
470/// ```
471///
472/// Default parameter values can also be set for optional parameters. This is
473/// done through the `#[php(defaults)]` attribute option. When an optional
474/// parameter has a default, it does not need to be a variant of `Option`:
475///
476/// ```rust,no_run,ignore
477/// # #![cfg_attr(windows, feature(abi_vectorcall))]
478/// # extern crate ext_php_rs;
479/// use ext_php_rs::prelude::*;
480///
481/// #[php_function]
482/// #[php(defaults(offset = 0))]
483/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
484///     let haystack: String = haystack.chars().skip(offset as usize).collect();
485///     haystack.find(needle)
486/// }
487///
488/// #[php_module]
489/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
490///     module.function(wrap_function!(rusty_strpos))
491/// }
492/// # fn main() {}
493/// ```
494///
495/// Note that if there is a non-optional argument after an argument that is a
496/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
497/// argument rather than an optional argument.
498///
499/// ```rust,no_run,ignore
500/// # #![cfg_attr(windows, feature(abi_vectorcall))]
501/// # extern crate ext_php_rs;
502/// use ext_php_rs::prelude::*;
503///
504/// /// `age` will be deemed required and nullable rather than optional.
505/// #[php_function]
506/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
507///     let mut greeting = format!("Hello, {}!", name);
508///
509///     if let Some(age) = age {
510///         greeting += &format!(" You are {} years old.", age);
511///     }
512///
513///     greeting += &format!(" {}.", description);
514///     greeting
515/// }
516///
517/// #[php_module]
518/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
519///     module.function(wrap_function!(greet))
520/// }
521/// # fn main() {}
522/// ```
523///
524/// You can also specify the optional arguments if you want to have nullable
525/// arguments before optional arguments. This is done through an attribute
526/// parameter:
527///
528/// ```rust,no_run,ignore
529/// # #![cfg_attr(windows, feature(abi_vectorcall))]
530/// # extern crate ext_php_rs;
531/// use ext_php_rs::prelude::*;
532///
533/// /// `age` will be deemed required and nullable rather than optional,
534/// /// while description will be optional.
535/// #[php_function]
536/// #[php(optional = "description")]
537/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
538///     let mut greeting = format!("Hello, {}!", name);
539///
540///     if let Some(age) = age {
541///         greeting += &format!(" You are {} years old.", age);
542///     }
543///
544///     if let Some(description) = description {
545///         greeting += &format!(" {}.", description);
546///     }
547///
548///     greeting
549/// }
550///
551/// #[php_module]
552/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
553///     module.function(wrap_function!(greet))
554/// }
555/// # fn main() {}
556/// ```
557///
558/// ## Variadic Functions
559///
560/// Variadic functions can be implemented by specifying the last argument in the
561/// Rust function to the type `&[&Zval]`. This is the equivalent of a PHP
562/// function using the `...$args` syntax.
563///
564/// ```rust,no_run,ignore
565/// # #![cfg_attr(windows, feature(abi_vectorcall))]
566/// # extern crate ext_php_rs;
567/// use ext_php_rs::{prelude::*, types::Zval};
568///
569/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
570/// #[php_function]
571/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
572///     // numbers is a slice of 4 Zvals all of type long
573///     number
574/// }
575///
576/// #[php_module]
577/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
578///     module.function(wrap_function!(add))
579/// }
580/// # fn main() {}
581/// ```
582///
583/// ## Returning `Result<T, E>`
584///
585/// You can also return a `Result` from the function. The error variant will be
586/// translated into an exception and thrown. See the section on
587/// [exceptions](../exceptions.md) for more details.
588// END DOCS FROM function.md
589#[proc_macro_attribute]
590pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
591    php_function_internal(args.into(), input.into()).into()
592}
593
594#[allow(clippy::needless_pass_by_value)]
595fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
596    let input = parse_macro_input2!(input as ItemFn);
597    if !args.is_empty() {
598        return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
599    }
600
601    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
602}
603
604// BEGIN DOCS FROM constant.md
605/// # `#[php_const]` Attribute
606///
607/// Exports a Rust constant as a global PHP constant. The constant can be any
608/// type that implements `IntoConst`.
609///
610/// The `wrap_constant!()` macro can be used to simplify the registration of
611/// constants. It sets the name and doc comments for the constant.
612///
613/// You can rename the const with options:
614///
615/// - `name` - Allows you to rename the property, e.g. `#[php(name =
616///   "new_name")]`
617/// - `change_case` - Allows you to rename the property using rename rules, e.g.
618///   `#[php(change_case = PascalCase)]`
619///
620/// ## Examples
621///
622/// ```rust,no_run,ignore
623/// # #![cfg_attr(windows, feature(abi_vectorcall))]
624/// # extern crate ext_php_rs;
625/// use ext_php_rs::prelude::*;
626///
627/// #[php_const]
628/// const TEST_CONSTANT: i32 = 100;
629///
630/// #[php_const]
631/// #[php(name = "I_AM_RENAMED")]
632/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
633///
634/// #[php_const]
635/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
636///
637/// #[php_module]
638/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
639///     module
640///         .constant(wrap_constant!(TEST_CONSTANT))
641///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
642///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
643/// }
644/// # fn main() {}
645/// ```
646///
647/// ## PHP usage
648///
649/// ```php
650/// <?php
651///
652/// var_dump(TEST_CONSTANT); // int(100)
653/// var_dump(I_AM_RENAMED); // int(42)
654/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
655/// ```
656// END DOCS FROM constant.md
657#[proc_macro_attribute]
658pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
659    php_const_internal(args.into(), input.into()).into()
660}
661
662#[allow(clippy::needless_pass_by_value)]
663fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
664    let input = parse_macro_input2!(input as ItemConst);
665    if !args.is_empty() {
666        return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
667    }
668
669    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
670}
671
672// BEGIN DOCS FROM module.md
673/// # `#[php_module]` Attribute
674///
675/// The module macro is used to annotate the `get_module` function, which is
676/// used by the PHP interpreter to retrieve information about your extension,
677/// including the name, version, functions and extra initialization functions.
678/// Regardless if you use this macro, your extension requires a `extern "C" fn
679/// get_module()` so that PHP can get this information.
680///
681/// The function is renamed to `get_module` if you have used another name. The
682/// function is passed an instance of `ModuleBuilder` which allows you to
683/// register the following (if required):
684///
685/// - Functions, classes, and constants
686/// - Extension and request startup and shutdown functions.
687///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
688/// - PHP extension information function
689///   - Used by the `phpinfo()` function to get information about your
690///     extension.
691///
692/// Classes and constants are not registered with PHP in the `get_module`
693/// function. These are registered inside the extension startup function.
694///
695/// ## Usage
696///
697/// ```rust,no_run,ignore
698/// # #![cfg_attr(windows, feature(abi_vectorcall))]
699/// # extern crate ext_php_rs;
700/// use ext_php_rs::{
701///     prelude::*,
702///     zend::ModuleEntry,
703///     info_table_start,
704///     info_table_row,
705///     info_table_end
706/// };
707///
708/// #[php_const]
709/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
710///
711/// #[php_class]
712/// pub struct Test {
713///     a: i32,
714///     b: i32
715/// }
716/// #[php_function]
717/// pub fn hello_world() -> &'static str {
718///     "Hello, world!"
719/// }
720///
721/// /// Used by the `phpinfo()` function and when you run `php -i`.
722/// /// This will probably be simplified with another macro eventually!
723/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
724///     info_table_start!();
725///     info_table_row!("my extension", "enabled");
726///     info_table_end!();
727/// }
728///
729/// #[php_module]
730/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
731///     module
732///         .constant(wrap_constant!(MY_CUSTOM_CONST))
733///         .class::<Test>()
734///         .function(wrap_function!(hello_world))
735///         .info_function(php_module_info)
736/// }
737/// # fn main() {}
738/// ```
739// END DOCS FROM module.md
740#[proc_macro_attribute]
741pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
742    php_module_internal(args.into(), input.into()).into()
743}
744
745#[allow(clippy::needless_pass_by_value)]
746fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
747    let input = parse_macro_input2!(input as ItemFn);
748    if !args.is_empty() {
749        return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
750    }
751
752    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
753}
754
755// BEGIN DOCS FROM impl.md
756/// # `#[php_impl]` Attribute
757///
758/// You can export an entire `impl` block to PHP. This exports all methods as
759/// well as constants to PHP on the class that it is implemented on. This
760/// requires the `#[php_class]` macro to already be used on the underlying
761/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
762/// block can be exported per class.
763///
764/// If you do not want a function exported to PHP, you should place it in a
765/// separate `impl` block.
766///
767/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
768/// &raquo;](./async_impl.md) for more info.
769///
770/// ## Options
771///
772/// By default all constants are renamed to `UPPER_CASE` and all methods are
773/// renamed to camelCase. This can be changed by passing the
774/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
775/// the `impl` block. The options are:
776///
777/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
778///   case.
779/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
780///   snake case.
781///
782/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
783/// for a list of all available cases.
784///
785/// ## Methods
786///
787/// Methods basically follow the same rules as functions, so read about the
788/// [`php_function`] macro first. The primary difference between functions and
789/// methods is they are bounded by their class object.
790///
791/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
792/// a consuming `self` parameter. Static methods can omit this `self` parameter.
793///
794/// To access the underlying Zend object, you can take a reference to a
795/// `ZendClassObject<T>` in place of the self parameter, where the parameter
796/// must be named `self_`. This can also be used to return a reference to
797/// `$this`.
798///
799/// The rest of the options are passed as separate attributes:
800///
801/// - `#[php(defaults(i = 5, b = "hello"))]` - Sets the default value for
802///   parameter(s).
803/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
804///   also sets the remaining parameters as optional, so all optional parameters
805///   must be a variant of `Option<T>`.
806/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
807///   "private")]` - Sets the visibility of the method.
808/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
809///   identifier, without renaming the Rust method name.
810///
811/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
812/// the equivalent function attribute parameters.
813///
814/// ### Constructors
815///
816/// By default, if a class does not have a constructor, it is not constructable
817/// from PHP. It can only be returned from a Rust function to PHP.
818///
819/// Constructors are Rust methods which can take any amount of parameters and
820/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
821/// When the error variant of `Result` is encountered, it is thrown as an
822/// exception and the class is not constructed.
823///
824/// Constructors are designated by either naming the method `__construct` or by
825/// annotating a method with the `#[php(constructor)]` attribute. Note that when
826/// using the attribute, the function is not exported to PHP like a regular
827/// method.
828///
829/// Constructors cannot use the visibility or rename attributes listed above.
830///
831/// ## Constants
832///
833/// Constants are defined as regular Rust `impl` constants. Any type that
834/// implements `IntoZval` can be used as a constant. Constant visibility is not
835/// supported at the moment, and therefore no attributes are valid on constants.
836///
837/// ## Property getters and setters
838///
839/// You can add properties to classes which use Rust functions as getters and/or
840/// setters. This is done with the `#[php(getter)]` and `#[php(setter)]`
841/// attributes. By default, the `get_` or `set_` prefix is trimmed from the
842/// start of the function name, and the remainder is used as the property name.
843///
844/// If you want to use a different name for the property, you can pass a `name`
845/// or `change_case` option to the `#[php]` attribute which will change the
846/// property name.
847///
848/// Properties do not necessarily have to have both a getter and a setter, if
849/// the property is immutable the setter can be omitted, and vice versa for
850/// getters.
851///
852/// The `#[php(getter)]` and `#[php(setter)]` attributes are mutually exclusive
853/// on methods. Properties cannot have multiple getters or setters, and the
854/// property name cannot conflict with field properties defined on the struct.
855///
856/// As the same as field properties, method property types must implement both
857/// `IntoZval` and `FromZval`.
858///
859/// ## Example
860///
861/// Continuing on from our `Human` example in the structs section, we will
862/// define a constructor, as well as getters for the properties. We will also
863/// define a constant for the maximum age of a `Human`.
864///
865/// ```rust,no_run,ignore
866/// # #![cfg_attr(windows, feature(abi_vectorcall))]
867/// # extern crate ext_php_rs;
868/// use ext_php_rs::{prelude::*, types::ZendClassObject};
869///
870/// #[php_class]
871/// #[derive(Debug, Default)]
872/// pub struct Human {
873///     name: String,
874///     age: i32,
875///     #[php(prop)]
876///     address: String,
877/// }
878///
879/// #[php_impl]
880/// impl Human {
881///     const MAX_AGE: i32 = 100;
882///
883///     // No `#[constructor]` attribute required here - the name is `__construct`.
884///     pub fn __construct(name: String, age: i32) -> Self {
885///         Self {
886///             name,
887///             age,
888///             address: String::new()
889///         }
890///     }
891///
892///     #[php(getter)]
893///     pub fn get_name(&self) -> String {
894///         self.name.to_string()
895///     }
896///
897///     #[php(setter)]
898///     pub fn set_name(&mut self, name: String) {
899///         self.name = name;
900///     }
901///
902///     #[php(getter)]
903///     pub fn get_age(&self) -> i32 {
904///         self.age
905///     }
906///
907///     pub fn introduce(&self) {
908///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
909///     }
910///
911///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
912///         dbg!(self_)
913///     }
914///
915///     pub fn get_max_age() -> i32 {
916///         Self::MAX_AGE
917///     }
918/// }
919/// #[php_module]
920/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
921///     module.class::<Human>()
922/// }
923/// # fn main() {}
924/// ```
925///
926/// Using our newly created class in PHP:
927///
928/// ```php
929/// <?php
930///
931/// $me = new Human('David', 20);
932///
933/// $me->introduce(); // My name is David and I am 20 years old.
934/// var_dump(Human::get_max_age()); // int(100)
935/// var_dump(Human::MAX_AGE); // int(100)
936/// ```
937///
938/// [`php_async_impl`]: ./async_impl.md
939// END DOCS FROM impl.md
940#[proc_macro_attribute]
941pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
942    php_impl_internal(args.into(), input.into()).into()
943}
944
945#[allow(clippy::needless_pass_by_value)]
946fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
947    let input = parse_macro_input2!(input as ItemImpl);
948    if !args.is_empty() {
949        return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
950    }
951
952    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
953}
954
955// BEGIN DOCS FROM extern.md
956/// # `#[php_extern]` Attribute
957///
958/// Attribute used to annotate `extern` blocks which are deemed as PHP
959/// functions.
960///
961/// This allows you to 'import' PHP functions into Rust so that they can be
962/// called like regular Rust functions. Parameters can be any type that
963/// implements [`IntoZval`], and the return type can be anything that implements
964/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
965/// case).
966///
967/// Unlike most other attributes, this does not need to be placed inside a
968/// `#[php_module]` block.
969///
970/// # Panics
971///
972/// The function can panic when called under a few circumstances:
973///
974/// * The function could not be found or was not callable.
975/// * One of the parameters could not be converted into a [`Zval`].
976/// * The actual function call failed internally.
977/// * The output [`Zval`] could not be parsed into the output type.
978///
979/// The last point can be important when interacting with functions that return
980/// unions, such as [`strpos`] which can return an integer or a boolean. In this
981/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
982/// invalid, and vice versa.
983///
984/// # Example
985///
986/// This `extern` block imports the [`strpos`] function from PHP. Notice that
987/// the string parameters can take either [`String`] or [`&str`], the optional
988/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
989/// as the return type is an integer-boolean union.
990///
991/// ```rust,no_run,ignore
992/// # #![cfg_attr(windows, feature(abi_vectorcall))]
993/// # extern crate ext_php_rs;
994/// use ext_php_rs::{
995///     prelude::*,
996///     types::Zval,
997/// };
998///
999/// #[php_extern]
1000/// extern "C" {
1001///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
1002/// }
1003///
1004/// #[php_function]
1005/// pub fn my_strpos() {
1006///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
1007/// }
1008///
1009/// #[php_module]
1010/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1011///     module.function(wrap_function!(my_strpos))
1012/// }
1013/// # fn main() {}
1014/// ```
1015///
1016/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
1017/// [`IntoZval`]: crate::convert::IntoZval
1018/// [`Zval`]: crate::types::Zval
1019// END DOCS FROM extern.md
1020#[proc_macro_attribute]
1021pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
1022    php_extern_internal(args.into(), input.into()).into()
1023}
1024
1025#[allow(clippy::needless_pass_by_value)]
1026fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
1027    let input = parse_macro_input2!(input as ItemForeignMod);
1028
1029    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
1030}
1031
1032// BEGIN DOCS FROM zval_convert.md
1033/// # `ZvalConvert` Derive Macro
1034///
1035/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
1036/// traits on a struct or enum.
1037///
1038/// ## Structs
1039///
1040/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
1041/// also implemented, mapping fields to properties in both directions. All
1042/// fields on the struct must implement `FromZval` as well. Generics are allowed
1043/// on structs that use the derive macro, however, the implementation will add a
1044/// `FromZval` bound to all generics types.
1045///
1046/// ### Examples
1047///
1048/// ```rust,no_run,ignore
1049/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1050/// # extern crate ext_php_rs;
1051/// use ext_php_rs::prelude::*;
1052///
1053/// #[derive(ZvalConvert)]
1054/// pub struct ExampleClass<'a> {
1055///     a: i32,
1056///     b: String,
1057///     c: &'a str
1058/// }
1059///
1060/// #[php_function]
1061/// pub fn take_object(obj: ExampleClass) {
1062///     dbg!(obj.a, obj.b, obj.c);
1063/// }
1064///
1065/// #[php_function]
1066/// pub fn give_object() -> ExampleClass<'static> {
1067///     ExampleClass {
1068///         a: 5,
1069///         b: "String".to_string(),
1070///         c: "Borrowed",
1071///     }
1072/// }
1073///
1074/// #[php_module]
1075/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1076///     module
1077///         .function(wrap_function!(take_object))
1078///         .function(wrap_function!(give_object))
1079/// }
1080/// # fn main() {}
1081/// ```
1082///
1083/// Calling from PHP:
1084///
1085/// ```php
1086/// <?php
1087///
1088/// $obj = new stdClass;
1089/// $obj->a = 5;
1090/// $obj->b = 'Hello, world!';
1091/// $obj->c = 'another string';
1092///
1093/// take_object($obj);
1094/// var_dump(give_object());
1095/// ```
1096///
1097/// Another example involving generics:
1098///
1099/// ```rust,no_run,ignore
1100/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1101/// # extern crate ext_php_rs;
1102/// use ext_php_rs::prelude::*;
1103///
1104/// // T must implement both `PartialEq<i32>` and `FromZval`.
1105/// #[derive(Debug, ZvalConvert)]
1106/// pub struct CompareVals<T: PartialEq<i32>> {
1107///     a: T,
1108///     b: T
1109/// }
1110///
1111/// #[php_function]
1112/// pub fn take_object(obj: CompareVals<i32>) {
1113///     dbg!(obj);
1114/// }
1115///
1116/// #[php_module]
1117/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1118///     module
1119///         .function(wrap_function!(take_object))
1120/// }
1121/// # fn main() {}
1122/// ```
1123///
1124/// ## Enums
1125///
1126/// When used on an enum, the `FromZval` implementation will treat the enum as a
1127/// tagged union with a mixed datatype. This allows you to accept multiple types
1128/// in a parameter, for example, a string and an integer.
1129///
1130/// The enum variants must not have named fields, and each variant must have
1131/// exactly one field (the type to extract from the zval). Optionally, the enum
1132/// may have one default variant with no data contained, which will be used when
1133/// the rest of the variants could not be extracted from the zval.
1134///
1135/// The ordering of the variants in the enum is important, as the `FromZval`
1136/// implementation will attempt to parse the zval data in order. For example, if
1137/// you put a `String` variant before an integer variant, the integer would be
1138/// converted to a string and passed as the string variant.
1139///
1140/// ### Examples
1141///
1142/// Basic example showing the importance of variant ordering and default field:
1143///
1144/// ```rust,no_run,ignore
1145/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1146/// # extern crate ext_php_rs;
1147/// use ext_php_rs::prelude::*;
1148///
1149/// #[derive(Debug, ZvalConvert)]
1150/// pub enum UnionExample<'a> {
1151///     Long(u64), // Long
1152///     ProperStr(&'a str), // Actual string - not a converted value
1153///     ParsedStr(String), // Potentially parsed string, i.e. a double
1154///     None // Zval did not contain anything that could be parsed above
1155/// }
1156///
1157/// #[php_function]
1158/// pub fn test_union(val: UnionExample) {
1159///     dbg!(val);
1160/// }
1161///
1162/// #[php_function]
1163/// pub fn give_union() -> UnionExample<'static> {
1164///     UnionExample::Long(5)
1165/// }
1166///
1167/// #[php_module]
1168/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1169///     module
1170///         .function(wrap_function!(test_union))
1171///         .function(wrap_function!(give_union))
1172/// }
1173/// # fn main() {}
1174/// ```
1175///
1176/// Use in PHP:
1177///
1178/// ```php
1179/// test_union(5); // UnionExample::Long(5)
1180/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
1181/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
1182/// test_union(null); // UnionExample::None
1183/// var_dump(give_union()); // int(5)
1184/// ```
1185// END DOCS FROM zval_convert.md
1186#[proc_macro_derive(ZvalConvert)]
1187pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
1188    zval_convert_derive_internal(input.into()).into()
1189}
1190
1191fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
1192    let input = parse_macro_input2!(input as DeriveInput);
1193
1194    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
1195}
1196
1197/// Defines an `extern` function with the Zend fastcall convention based on
1198/// operating system.
1199///
1200/// On Windows, Zend fastcall functions use the vector calling convention, while
1201/// on all other operating systems no fastcall convention is used (just the
1202/// regular C calling convention).
1203///
1204/// This macro wraps a function and applies the correct calling convention.
1205///
1206/// ## Examples
1207///
1208/// ```rust,ignore
1209/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1210/// use ext_php_rs::zend_fastcall;
1211///
1212/// zend_fastcall! {
1213///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
1214///         a + b
1215///     }
1216/// }
1217/// ```
1218///
1219/// On Windows, this function will have the signature `pub extern "vectorcall"
1220/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
1221/// signature `pub extern "C" fn(i32, i32) -> i32`.
1222///
1223/// ## Support
1224///
1225/// The `vectorcall` ABI is currently only supported on Windows with nightly
1226/// Rust and the `abi_vectorcall` feature enabled.
1227#[proc_macro]
1228pub fn zend_fastcall(input: TokenStream) -> TokenStream {
1229    zend_fastcall_internal(input.into()).into()
1230}
1231
1232fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
1233    let input = parse_macro_input2!(input as ItemFn);
1234
1235    fastcall::parser(input)
1236}
1237
1238/// Wraps a function to be used in the [`Module::function`] method.
1239#[proc_macro]
1240pub fn wrap_function(input: TokenStream) -> TokenStream {
1241    wrap_function_internal(input.into()).into()
1242}
1243
1244fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
1245    let input = parse_macro_input2!(input as syn::Path);
1246
1247    match function::wrap(&input) {
1248        Ok(parsed) => parsed,
1249        Err(e) => e.to_compile_error(),
1250    }
1251}
1252
1253/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
1254#[proc_macro]
1255pub fn wrap_constant(input: TokenStream) -> TokenStream {
1256    wrap_constant_internal(input.into()).into()
1257}
1258
1259fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1260    let input = parse_macro_input2!(input as syn::Path);
1261
1262    match constant::wrap(&input) {
1263        Ok(parsed) => parsed,
1264        Err(e) => e.to_compile_error(),
1265    }
1266}
1267
1268macro_rules! parse_macro_input2 {
1269    ($tokenstream:ident as $ty:ty) => {
1270        match syn::parse2::<$ty>($tokenstream) {
1271            Ok(data) => data,
1272            Err(err) => {
1273                return proc_macro2::TokenStream::from(err.to_compile_error());
1274            }
1275        }
1276    };
1277    ($tokenstream:ident) => {
1278        $crate::parse_macro_input!($tokenstream as _)
1279    };
1280}
1281
1282pub(crate) use parse_macro_input2;
1283
1284macro_rules! err {
1285    ($span:expr => $($msg:tt)*) => {
1286        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
1287    };
1288    ($($msg:tt)*) => {
1289        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
1290    };
1291}
1292
1293/// Bails out of a function with a syn error.
1294macro_rules! bail {
1295    ($span:expr => $($msg:tt)*) => {
1296        return Err($crate::err!($span => $($msg)*))
1297    };
1298    ($($msg:tt)*) => {
1299        return Err($crate::err!($($msg)*))
1300    };
1301}
1302
1303pub(crate) use bail;
1304pub(crate) use err;
1305
1306pub(crate) mod prelude {
1307    pub(crate) trait OptionTokens {
1308        fn option_tokens(&self) -> proc_macro2::TokenStream;
1309    }
1310
1311    impl<T: quote::ToTokens> OptionTokens for Option<T> {
1312        fn option_tokens(&self) -> proc_macro2::TokenStream {
1313            if let Some(token) = self {
1314                quote::quote! { ::std::option::Option::Some(#token) }
1315            } else {
1316                quote::quote! { ::std::option::Option::None }
1317            }
1318        }
1319    }
1320
1321    pub(crate) use crate::{bail, err};
1322    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
1323}
1324
1325#[cfg(test)]
1326mod tests {
1327    use super::*;
1328    use std::path::PathBuf;
1329
1330    type AttributeFn =
1331        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1332    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1333
1334    #[test]
1335    pub fn test_expand() {
1336        macrotest::expand("tests/expand/*.rs");
1337        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1338            let entry = entry.expect("Failed to read expand test file");
1339            runtime_expand_attr(&entry);
1340            runtime_expand_func(&entry);
1341            runtime_expand_derive(&entry);
1342        }
1343    }
1344
1345    fn runtime_expand_attr(path: &PathBuf) {
1346        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1347        runtime_macros::emulate_attributelike_macro_expansion(
1348            file,
1349            &[
1350                ("php_class", php_class_internal as AttributeFn),
1351                ("php_const", php_const_internal as AttributeFn),
1352                ("php_enum", php_enum_internal as AttributeFn),
1353                ("php_interface", php_interface_internal as AttributeFn),
1354                ("php_extern", php_extern_internal as AttributeFn),
1355                ("php_function", php_function_internal as AttributeFn),
1356                ("php_impl", php_impl_internal as AttributeFn),
1357                ("php_module", php_module_internal as AttributeFn),
1358            ],
1359        )
1360        .expect("Failed to expand attribute macros in test file");
1361    }
1362
1363    fn runtime_expand_func(path: &PathBuf) {
1364        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1365        runtime_macros::emulate_functionlike_macro_expansion(
1366            file,
1367            &[
1368                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1369                ("wrap_function", wrap_function_internal as FunctionLikeFn),
1370                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1371            ],
1372        )
1373        .expect("Failed to expand function-like macros in test file");
1374    }
1375
1376    fn runtime_expand_derive(path: &PathBuf) {
1377        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1378        runtime_macros::emulate_derive_macro_expansion(
1379            file,
1380            &[("ZvalConvert", zval_convert_derive_internal)],
1381        )
1382        .expect("Failed to expand derive macros in test file");
1383    }
1384}