Skip to main content

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 impl_interface;
11mod interface;
12mod module;
13mod parsing;
14mod syn_ext;
15mod zval;
16
17use darling::FromMeta;
18use proc_macro::TokenStream;
19use proc_macro2::TokenStream as TokenStream2;
20use syn::{
21    DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, ItemTrait,
22};
23
24extern crate proc_macro;
25
26// BEGIN DOCS FROM classes.md
27/// # `#[php_class]` Attribute
28///
29/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
30/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
31/// well as registering the class to be registered with the `#[php_module]`
32/// macro.
33///
34/// ## Options
35///
36/// There are additional macros that modify the class. These macros **must** be
37/// placed underneath the `#[php_class]` attribute.
38///
39/// - `name` - Changes the name of the class when exported to PHP. The Rust
40///   struct name is kept the same. If no name is given, the name of the struct
41///   is used. Useful for namespacing classes.
42/// - `change_case` - Changes the case of the class name when exported to PHP.
43/// - `readonly` - Marks the class as readonly (PHP 8.2+). All properties in a
44///   readonly class are implicitly readonly.
45/// - `flags` - Sets class flags using `ClassFlags`, e.g. `#[php(flags =
46///   ClassFlags::Final)]` for a final class.
47/// - `#[php(extends(...))]` - Sets the parent class of the class. Can only be
48///   used once. Two forms are supported:
49///   - Simple type form: `#[php(extends(MyBaseClass))]` - For Rust-defined
50///     classes that implement `RegisteredClass`.
51///   - Explicit form: `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - For
52///     built-in PHP classes. `ce_fn` must be a function with the signature
53///     `fn() -> &'static ClassEntry`.
54/// - `#[php(implements(...))]` - Implements the given interface on the class.
55///   Can be used multiple times. Two forms are supported:
56///   - Simple type form: `#[php(implements(MyInterface))]` — For Rust-defined
57///     interfaces that implement `RegisteredClass`.
58///   - Explicit form: `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]`
59///     — For built-in PHP interfaces. `ce_fn` must be a valid function with the
60///     signature `fn() -> &'static ClassEntry`.
61/// - Generated stubs write each parent class and interface name as a fully
62///   qualified name, with a leading `\`. A leading `\` in `stub = "..."` is
63///   optional.
64///
65/// You may also use the `#[php(prop)]` attribute on a struct field to use the
66/// field as a PHP property. By default, the property is public and its name is
67/// the field name converted to camelCase: the field `first_name` gives the
68/// property `firstName`. Property types must implement `IntoZval` and
69/// `FromZval`.
70///
71/// You can customize properties with these options:
72///
73/// - `name` - Allows you to rename the property, e.g. `#[php(prop, name =
74///   "new_name")]`
75/// - `change_case` - Allows you to rename the property using rename rules, e.g.
76///   `#[php(prop, change_case = "PascalCase")]`
77/// - `static` - Makes the property static (shared across all instances), e.g.
78///   `#[php(prop, static)]`
79/// - `flags` - Sets property visibility flags, e.g. `#[php(prop, flags =
80///   ext_php_rs::flags::PropertyFlags::Private)]`
81///
82/// ### How PHP code uses a Rust property
83///
84/// The value of a `#[php(prop)]` field is in the Rust struct. PHP reads the
85/// value with the getter and writes the value with the setter.
86///
87/// These operations use the getter and the setter:
88///
89/// - `$obj->prop += 1`, `$obj->prop .= 'x'`, `$obj->prop++` and `$obj->prop ??=
90///   1`.
91/// - `==` and `foreach`, which use the current Rust values. Thus `==` calls
92///   each getter of the two objects.
93///
94/// These operations change only a copy, and the Rust value does not change:
95///
96/// - `$obj->prop[] = 1`, `$obj->prop['key'] = 1` and `$r = &$obj->prop`. PHP
97///   gives the notice "Indirect modification of overloaded property", as for
98///   `__get`.
99/// - `foreach ($obj as &$value)`.
100///
101/// These operations throw an `Error`:
102///
103/// - `$obj->prop = &$value`.
104/// - `unset($obj->prop)`, because the Rust value cannot be removed.
105///
106/// If a PHP subclass declares the property again with hooks, PHP uses the Rust
107/// getter and setter, not the hooks.
108///
109/// ## Restrictions
110///
111/// ### No lifetime parameters
112///
113/// Rust lifetimes are used by the Rust compiler to reason about a program's
114/// memory safety. They are a compile-time only concept;
115/// there is no way to access Rust lifetimes at runtime from a dynamic language
116/// like PHP.
117///
118/// As soon as Rust data is exposed to PHP,
119/// there is no guarantee which the Rust compiler can make on how long the data
120/// will live. PHP is a reference-counted language and those references can be
121/// held for an arbitrarily long time, which is untraceable by the Rust
122/// compiler. The only possible way to express this correctly is to require that
123/// any `#[php_class]` does not borrow data for any lifetime shorter than the
124/// `'static` lifetime, i.e. the `#[php_class]` cannot have any lifetime
125/// parameters.
126///
127/// When you need to share ownership of data between PHP and Rust,
128/// instead of using borrowed references with lifetimes, consider using
129/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
130///
131/// ### No generic parameters
132///
133/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled
134/// implementations each time it is used with a different concrete type for `T`.
135/// These new implementations are generated by the compiler at each usage site.
136/// This is incompatible with wrapping `Foo` in PHP,
137/// where there needs to be a single compiled implementation of `Foo` which is
138/// integrated with the PHP interpreter.
139///
140/// ## Example
141///
142/// This example creates a PHP class `Human`, adding a PHP property `address`.
143///
144/// ```rust,no_run,ignore
145/// # #![cfg_attr(windows, feature(abi_vectorcall))]
146/// # extern crate ext_php_rs;
147/// use ext_php_rs::prelude::*;
148///
149/// #[php_class]
150/// pub struct Human {
151///     name: String,
152///     age: i32,
153///     #[php(prop)]
154///     address: String,
155/// }
156///
157/// #[php_module]
158/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
159///     module.class::<Human>()
160/// }
161/// # fn main() {}
162/// ```
163///
164/// Create a custom exception `RedisException`, which extends `Exception`, and
165/// put it in the `Redis\Exception` namespace:
166///
167/// ```rust,no_run,ignore
168/// # #![cfg_attr(windows, feature(abi_vectorcall))]
169/// # extern crate ext_php_rs;
170/// use ext_php_rs::{
171///     prelude::*,
172///     exception::PhpException,
173///     zend::ce
174/// };
175///
176/// #[php_class]
177/// #[php(name = "Redis\\Exception\\RedisException")]
178/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
179/// #[derive(Default)]
180/// pub struct RedisException;
181///
182/// // Throw our newly created exception
183/// #[php_function]
184/// pub fn throw_exception() -> PhpResult<i32> {
185///     Err(PhpException::from_class::<RedisException>("Not good!".into()))
186/// }
187///
188/// #[php_module]
189/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
190///     module
191///         .class::<RedisException>()
192///         .function(wrap_function!(throw_exception))
193/// }
194/// # fn main() {}
195/// ```
196///
197/// ### Extending a Rust-defined Class
198///
199/// When extending another Rust-defined class, you can use the simpler type
200/// syntax:
201///
202/// ```rust,ignore
203/// # #![cfg_attr(windows, feature(abi_vectorcall))]
204/// # extern crate ext_php_rs;
205/// use ext_php_rs::prelude::*;
206///
207/// #[php_class]
208/// #[derive(Default)]
209/// pub struct Animal;
210///
211/// #[php_impl]
212/// impl Animal {
213///     pub fn speak(&self) -> &'static str {
214///         "..."
215///     }
216/// }
217///
218/// #[php_class]
219/// #[php(extends(Animal))]
220/// #[derive(Default)]
221/// pub struct Dog;
222///
223/// #[php_impl]
224/// impl Dog {
225///     pub fn speak(&self) -> &'static str {
226///         "Woof!"
227///     }
228/// }
229///
230/// #[php_module]
231/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
232///     module
233///         .class::<Animal>()
234///         .class::<Dog>()
235/// }
236/// # fn main() {}
237/// ```
238///
239/// #### Sharing Methods Between Parent and Child Classes
240///
241/// When both parent and child are Rust-defined classes, methods defined only in
242/// the parent won't automatically work when called on a child instance. This is
243/// because each Rust type has its own object handlers.
244///
245/// The recommended workaround is to use a Rust trait for shared behavior:
246///
247/// ```rust,ignore
248/// # #![cfg_attr(windows, feature(abi_vectorcall))]
249/// # extern crate ext_php_rs;
250/// use ext_php_rs::prelude::*;
251///
252/// /// Trait for shared behavior
253/// trait AnimalBehavior {
254///     fn speak(&self) -> &'static str {
255///         "..."
256///     }
257/// }
258///
259/// #[php_class]
260/// #[derive(Default)]
261/// pub struct Animal;
262///
263/// impl AnimalBehavior for Animal {}
264///
265/// #[php_impl]
266/// impl Animal {
267///     pub fn speak(&self) -> &'static str {
268///         AnimalBehavior::speak(self)
269///     }
270/// }
271///
272/// #[php_class]
273/// #[php(extends(Animal))]
274/// #[derive(Default)]
275/// pub struct Dog;
276///
277/// impl AnimalBehavior for Dog {
278///     fn speak(&self) -> &'static str {
279///         "Woof!"
280///     }
281/// }
282///
283/// #[php_impl]
284/// impl Dog {
285///     // Re-export the method so it works on Dog instances
286///     pub fn speak(&self) -> &'static str {
287///         AnimalBehavior::speak(self)
288///     }
289/// }
290///
291/// #[php_module]
292/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
293///     module
294///         .class::<Animal>()
295///         .class::<Dog>()
296/// }
297/// # fn main() {}
298/// ```
299///
300/// This pattern ensures that:
301/// - `$animal->speak()` returns `"..."`
302/// - `$dog->speak()` returns `"Woof!"`
303/// - `$dog instanceof Animal` is `true`
304///
305/// ## Implementing an Interface
306///
307/// To implement an interface, use `#[php(implements(...))]`. For built-in PHP
308/// interfaces, use the explicit form with `ce` and `stub`. For Rust-defined
309/// interfaces, you can use the simple type form. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
310///
311/// ````rust,no_run,ignore
312/// # #![cfg_attr(windows, feature(abi_vectorcall))]
313/// # extern crate ext_php_rs;
314/// use ext_php_rs::{
315///     prelude::*,
316///     exception::PhpResult,
317///     types::Zval,
318///     zend::ce,
319/// };
320///
321/// #[php_class]
322/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
323/// #[derive(Default)]
324/// pub struct EvenNumbersArray;
325///
326/// /// Returns `true` if the array offset is an even number.
327/// /// Usage:
328/// /// ```php
329/// /// $arr = new EvenNumbersArray();
330/// /// var_dump($arr[0]); // true
331/// /// var_dump($arr[1]); // false
332/// /// var_dump($arr[2]); // true
333/// /// var_dump($arr[3]); // false
334/// /// var_dump($arr[4]); // true
335/// /// var_dump($arr[5] = true); // Fatal error:  Uncaught Exception: Setting values is not supported
336/// /// ```
337/// #[php_impl]
338/// impl EvenNumbersArray {
339///     pub fn __construct() -> EvenNumbersArray {
340///         EvenNumbersArray {}
341///     }
342///     // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
343///     pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
344///         offset.is_long()
345///     }
346///     pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
347///         let integer_offset = offset.long().ok_or("Expected integer offset")?;
348///         Ok(integer_offset % 2 == 0)
349///     }
350///     pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
351///         Err("Setting values is not supported".into())
352///     }
353///     pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
354///         Err("Setting values is not supported".into())
355///     }
356/// }
357///
358/// #[php_module]
359/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
360///     module.class::<EvenNumbersArray>()
361/// }
362/// # fn main() {}
363/// ````
364///
365/// ## Static Properties
366///
367/// Static properties are shared across all instances of a class. Use
368/// `#[php(prop, static)]` to declare a static property. Unlike instance
369/// properties, static properties are managed entirely by PHP and do not use
370/// Rust property handlers.
371///
372/// You can specify a default value using the `default` attribute:
373///
374/// ```rust,no_run,ignore
375/// # #![cfg_attr(windows, feature(abi_vectorcall))]
376/// # extern crate ext_php_rs;
377/// use ext_php_rs::prelude::*;
378/// use ext_php_rs::class::RegisteredClass;
379///
380/// #[php_class]
381/// pub struct Counter {
382///     #[php(prop)]
383///     pub instance_value: i32,
384///     #[php(prop, static, default = 0)]
385///     pub count: i32,
386///     #[php(prop, static, flags = ext_php_rs::flags::PropertyFlags::Private)]
387///     pub internal_state: String,
388/// }
389///
390/// #[php_impl]
391/// impl Counter {
392///     pub fn __construct(value: i32) -> Self {
393///         Self {
394///             instance_value: value,
395///             count: 0,
396///             internal_state: String::new(),
397///         }
398///     }
399///
400///     /// Increment the static counter from Rust
401///     pub fn increment() {
402///         let ce = Self::get_metadata().ce();
403///         let current: i64 = ce.get_static_property("count").unwrap_or(0);
404///         ce.set_static_property("count", current + 1).unwrap();
405///     }
406///
407///     /// Get the current count
408///     pub fn get_count() -> i64 {
409///         let ce = Self::get_metadata().ce();
410///         ce.get_static_property("count").unwrap_or(0)
411///     }
412/// }
413///
414/// #[php_module]
415/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
416///     module.class::<Counter>()
417/// }
418/// # fn main() {}
419/// ```
420///
421/// From PHP, you can access static properties directly on the class:
422///
423/// ```php
424/// // No need to initialize - count already has default value of 0
425/// Counter::increment();
426/// Counter::increment();
427/// echo Counter::$count; // 2
428/// echo Counter::getCount(); // 2
429/// ```
430///
431/// ## Abstract Classes
432///
433/// Abstract classes cannot be instantiated directly and may contain abstract
434/// methods that must be implemented by subclasses. Use `#[php(flags =
435/// ClassFlags::Abstract)]` to declare an abstract class:
436///
437/// ```rust,ignore
438/// use ext_php_rs::prelude::*;
439/// use ext_php_rs::flags::ClassFlags;
440///
441/// #[php_class]
442/// #[php(flags = ClassFlags::Abstract)]
443/// pub struct AbstractAnimal;
444///
445/// #[php_impl]
446/// impl AbstractAnimal {
447///     // Protected constructor for subclasses
448///     #[php(vis = "protected")]
449///     pub fn __construct() -> Self {
450///         Self
451///     }
452///
453///     // Abstract method - must be implemented by subclasses.
454///     // Body is never called; use unimplemented!() as a placeholder.
455///     #[php(abstract)]
456///     pub fn speak(&self) -> String {
457///         unimplemented!()
458///     }
459///
460///     // Concrete method - inherited by subclasses
461///     pub fn breathe(&self) {
462///         println!("Breathing...");
463///     }
464/// }
465/// ```
466///
467/// From PHP, you can extend this abstract class:
468///
469/// ```php
470/// class Dog extends AbstractAnimal {
471///     public function __construct() {
472///         parent::__construct();
473///     }
474///
475///     public function speak(): string {
476///         return "Woof!";
477///     }
478/// }
479///
480/// $dog = new Dog();
481/// echo $dog->speak(); // "Woof!"
482/// $dog->breathe();    // "Breathing..."
483///
484/// // This would cause an error:
485/// // $animal = new AbstractAnimal(); // Cannot instantiate abstract class
486/// ```
487///
488/// See the [impl documentation](./impl.md#abstract-methods) for more details on
489/// abstract methods.
490///
491/// ## Final Classes
492///
493/// Final classes cannot be extended. Use `#[php(flags = ClassFlags::Final)]` to
494/// declare a final class:
495///
496/// ```rust,ignore
497/// use ext_php_rs::prelude::*;
498/// use ext_php_rs::flags::ClassFlags;
499///
500/// #[php_class]
501/// #[php(flags = ClassFlags::Final)]
502/// pub struct FinalClass;
503/// ```
504///
505/// ## Readonly Classes (PHP 8.2+)
506///
507/// PHP 8.2 introduced [readonly classes](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.readonly),
508/// where all properties are implicitly readonly. You can create a readonly
509/// class using the `#[php(readonly)]` attribute:
510///
511/// ```rust,ignore
512/// # #![cfg_attr(windows, feature(abi_vectorcall))]
513/// # extern crate ext_php_rs;
514/// use ext_php_rs::prelude::*;
515///
516/// #[php_class]
517/// #[php(readonly)]
518/// pub struct ImmutablePoint {
519///     x: f64,
520///     y: f64,
521/// }
522///
523/// #[php_impl]
524/// impl ImmutablePoint {
525///     pub fn __construct(x: f64, y: f64) -> Self {
526///         Self { x, y }
527///     }
528///
529///     pub fn get_x(&self) -> f64 {
530///         self.x
531///     }
532///
533///     pub fn get_y(&self) -> f64 {
534///         self.y
535///     }
536///
537///     pub fn distance_from_origin(&self) -> f64 {
538///         (self.x * self.x + self.y * self.y).sqrt()
539///     }
540/// }
541///
542/// #[php_module]
543/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
544///     module.class::<ImmutablePoint>()
545/// }
546/// # fn main() {}
547/// ```
548///
549/// From PHP:
550///
551/// ```php
552/// $point = new ImmutablePoint(3.0, 4.0);
553/// echo $point->getX(); // 3.0
554/// echo $point->getY(); // 4.0
555/// echo $point->distanceFromOrigin(); // 5.0
556///
557/// // On PHP 8.2+, you can verify the class is readonly:
558/// $reflection = new ReflectionClass(ImmutablePoint::class);
559/// var_dump($reflection->isReadOnly()); // true
560/// ```
561///
562/// The `readonly` attribute is compatible with other class attributes:
563///
564/// ```rust,ignore
565/// # #![cfg_attr(windows, feature(abi_vectorcall))]
566/// # extern crate ext_php_rs;
567/// use ext_php_rs::prelude::*;
568/// use ext_php_rs::flags::ClassFlags;
569///
570/// // Readonly + Final class
571/// #[php_class]
572/// #[php(readonly)]
573/// #[php(flags = ClassFlags::Final)]
574/// pub struct FinalImmutableData {
575///     value: String,
576/// }
577/// # fn main() {}
578/// ```
579///
580/// **Note:** The `readonly` attribute requires PHP 8.2 or later. Using it when
581/// compiling against an earlier PHP version will result in a compile error.
582///
583/// ### Conditional Compilation for Multi-Version Support
584///
585/// If your extension needs to support both PHP 8.1 and PHP 8.2+, you can use
586/// conditional compilation to only enable readonly on supported versions.
587///
588/// First, add `ext-php-rs-build` as a build dependency in your `Cargo.toml`:
589///
590/// ```toml
591/// [build-dependencies]
592/// ext-php-rs-build = "0.1"
593/// anyhow = "1"
594/// ```
595///
596/// Then create a `build.rs` that detects the PHP version and emits cfg flags:
597///
598/// ```rust,ignore
599/// use ext_php_rs_build::{find_php, PHPInfo, ApiVersion, emit_php_cfg_flags, emit_check_cfg};
600///
601/// fn main() -> anyhow::Result<()> {
602///     let php = find_php()?;
603///     let info = PHPInfo::get(&php)?;
604///     let version: ApiVersion = info.zend_version()?.try_into()?;
605///
606///     emit_check_cfg();
607///     emit_php_cfg_flags(version);
608///     Ok(())
609/// }
610/// ```
611///
612/// Now you can use `#[cfg(php82)]` to conditionally apply the readonly
613/// attribute:
614///
615/// ```rust,ignore
616/// #[php_class]
617/// #[cfg_attr(php82, php(readonly))]
618/// pub struct MaybeReadonlyClass {
619///     value: String,
620/// }
621/// ```
622///
623/// The `ext-php-rs-build` crate provides several useful utilities:
624///
625/// - `find_php()` - Locates the PHP executable (respects the `PHP` env var)
626/// - `PHPInfo::get()` - Runs `php -i` and parses the output
627/// - `ApiVersion` - Enum representing PHP versions (Php80, Php81, Php82, etc.)
628/// - `emit_php_cfg_flags()` - Emits `cargo:rustc-cfg=phpXX` for all supported
629///   versions
630/// - `emit_check_cfg()` - Emits check-cfg to avoid unknown cfg warnings
631///
632/// This is **optional** - if your extension only targets PHP 8.2+, you can use
633/// `#[php(readonly)]` directly without any build script setup.
634///
635/// ## Cloning
636///
637/// PHP's native `clone` operator is supported for `#[php_class]` structs that
638/// implement `Clone`. A derive or a hand-written `impl Clone` both work, and
639/// the spelling of the derive path does not matter. The cloned PHP object
640/// contains a copy of the Rust data:
641///
642/// ```rust,ignore
643/// use ext_php_rs::prelude::*;
644///
645/// #[php_class]
646/// #[derive(Clone)]
647/// pub struct Style {
648///     #[php(prop)]
649///     pub font_size: f64,
650///     #[php(prop)]
651///     pub color: String,
652/// }
653///
654/// #[php_impl]
655/// impl Style {
656///     pub fn __construct(font_size: f64, color: String) -> Self {
657///         Self { font_size, color }
658///     }
659/// }
660/// ```
661///
662/// ```php
663/// $style = new Style(12.0, 'red');
664/// $copy = clone $style;
665///
666/// $copy->fontSize = 16.0;
667/// echo $style->fontSize;  // 12.0 — original is unchanged
668/// ```
669///
670/// Structs that do not implement `Clone` throw an error when cloned:
671///
672/// ```php
673/// // If MyClass does not implement Clone:
674/// $obj = new MyClass();
675/// $copy = clone $obj; // Error: Trying to clone an uncloneable object of class MyClass
676/// ```
677///
678/// ## Implementing Iterator
679///
680/// To make a Rust class usable with PHP's `foreach` loop, implement the
681/// [`Iterator`](https://www.php.net/manual/en/class.iterator.php) interface.
682/// This requires implementing five methods: `current()`, `key()`, `next()`,
683/// `rewind()`, and `valid()`.
684///
685/// The following example creates a `RangeIterator` that iterates over a range
686/// of integers:
687///
688/// ````rust,no_run,ignore
689/// # #![cfg_attr(windows, feature(abi_vectorcall))]
690/// # extern crate ext_php_rs;
691/// use ext_php_rs::{prelude::*, zend::ce};
692///
693/// #[php_class]
694/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
695/// pub struct RangeIterator {
696///     start: i64,
697///     end: i64,
698///     current: i64,
699///     index: i64,
700/// }
701///
702/// #[php_impl]
703/// impl RangeIterator {
704///     /// Create a new range iterator from start to end (inclusive).
705///     pub fn __construct(start: i64, end: i64) -> Self {
706///         Self {
707///             start,
708///             end,
709///             current: start,
710///             index: 0,
711///         }
712///     }
713///
714///     /// Return the current element.
715///     pub fn current(&self) -> i64 {
716///         self.current
717///     }
718///
719///     /// Return the key of the current element.
720///     pub fn key(&self) -> i64 {
721///         self.index
722///     }
723///
724///     /// Move forward to next element.
725///     pub fn next(&mut self) {
726///         self.current += 1;
727///         self.index += 1;
728///     }
729///
730///     /// Rewind the Iterator to the first element.
731///     pub fn rewind(&mut self) {
732///         self.current = self.start;
733///         self.index = 0;
734///     }
735///
736///     /// Checks if current position is valid.
737///     pub fn valid(&self) -> bool {
738///         self.current <= self.end
739///     }
740/// }
741///
742/// #[php_module]
743/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
744///     module.class::<RangeIterator>()
745/// }
746/// # fn main() {}
747/// ````
748///
749/// Using the iterator in PHP:
750///
751/// ```php
752/// <?php
753///
754/// $range = new RangeIterator(1, 5);
755///
756/// // Use with foreach
757/// foreach ($range as $key => $value) {
758///     echo "$key => $value\n";
759/// }
760/// // Output:
761/// // 0 => 1
762/// // 1 => 2
763/// // 2 => 3
764/// // 3 => 4
765/// // 4 => 5
766///
767/// // Works with iterator functions
768/// $arr = iterator_to_array(new RangeIterator(10, 12));
769/// // [0 => 10, 1 => 11, 2 => 12]
770///
771/// $count = iterator_count(new RangeIterator(1, 100));
772/// // 100
773/// ```
774///
775/// ### Iterator with Mixed Types
776///
777/// You can return different types for keys and values. The following example
778/// uses string keys:
779///
780/// ````rust,no_run,ignore
781/// # #![cfg_attr(windows, feature(abi_vectorcall))]
782/// # extern crate ext_php_rs;
783/// use ext_php_rs::{prelude::*, zend::ce};
784///
785/// #[php_class]
786/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
787/// pub struct MapIterator {
788///     keys: Vec<String>,
789///     values: Vec<String>,
790///     index: usize,
791/// }
792///
793/// #[php_impl]
794/// impl MapIterator {
795///     pub fn __construct() -> Self {
796///         Self {
797///             keys: vec!["first".into(), "second".into(), "third".into()],
798///             values: vec!["one".into(), "two".into(), "three".into()],
799///             index: 0,
800///         }
801///     }
802///
803///     pub fn current(&self) -> Option<String> {
804///         self.values.get(self.index).cloned()
805///     }
806///
807///     pub fn key(&self) -> Option<String> {
808///         self.keys.get(self.index).cloned()
809///     }
810///
811///     pub fn next(&mut self) {
812///         self.index += 1;
813///     }
814///
815///     pub fn rewind(&mut self) {
816///         self.index = 0;
817///     }
818///
819///     pub fn valid(&self) -> bool {
820///         self.index < self.keys.len()
821///     }
822/// }
823///
824/// #[php_module]
825/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
826///     module.class::<MapIterator>()
827/// }
828/// # fn main() {}
829/// ````
830///
831/// ```php
832/// <?php
833///
834/// $map = new MapIterator();
835/// foreach ($map as $key => $value) {
836///     echo "$key => $value\n";
837/// }
838/// // Output:
839/// // first => one
840/// // second => two
841/// // third => three
842/// ```
843// END DOCS FROM classes.md
844#[proc_macro_attribute]
845pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
846    php_class_internal(args.into(), input.into()).into()
847}
848
849#[allow(clippy::needless_pass_by_value)]
850fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
851    let input = parse_macro_input2!(input as ItemStruct);
852    if let Err(e) = reject_args(&args, "php_class", PHP_ATTR_HINT) {
853        return e.to_compile_error();
854    }
855
856    class::parser(input).unwrap_or_else(|e| e.to_compile_error())
857}
858
859// BEGIN DOCS FROM enum.md
860/// # `#[php_enum]` Attribute
861///
862/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute
863/// macro. This attribute derives the `RegisteredClass` and `PhpEnum` traits on
864/// your enum. To register the enum use the `enumeration::<EnumName>()` method
865/// on the `ModuleBuilder` in the `#[php_module]` macro.
866///
867/// ## Options
868///
869/// The `#[php_enum]` attribute can be configured with the following options:
870/// - `#[php(name = "EnumName")]` or `#[php(change_case = "snake_case")]`: Sets
871///   the name of the enum in PHP. The default is the `PascalCase` name of the
872///   enum.
873/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust
874///   discriminants (e.g., `Hearts = 1`).
875/// - `#[php(change_cases_case = "snake_case")]`: Sets the rename rule for every
876///   case that has no `name` or `change_case` of its own. The old name of this
877///   option, `rename_cases`, is a compile error.
878///
879/// PHP enums are always public. `#[php(vis = "...")]` on an enum is a compile
880/// error, and so is any argument passed to the macro itself, such as
881/// `#[php_enum(name = "Suit")]`.
882///
883/// The cases of the enum can be configured with the following options:
884/// - `#[php(name = "CaseName")]` or `#[php(change_case = "snake_case")]`: Sets
885///   the name of the enum case in PHP. The default is the `PascalCase` name of
886///   the case.
887/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant
888///   value for the enum case. This can be a string or an integer. If not set,
889///   the case will be exported as a simple enum case without a discriminant.
890///
891/// ### Example
892///
893/// This example creates a PHP enum `Suit`.
894///
895/// ```rust,no_run,ignore
896/// # #![cfg_attr(windows, feature(abi_vectorcall))]
897/// # extern crate ext_php_rs;
898/// use ext_php_rs::prelude::*;
899///
900/// #[php_enum]
901/// pub enum Suit {
902///     Hearts,
903///     Diamonds,
904///     Clubs,
905///     Spades,
906/// }
907///
908/// #[php_module]
909/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
910///     module.enumeration::<Suit>()
911/// }
912/// # fn main() {}
913/// ```
914///
915/// ## Backed Enums
916/// Enums can also be backed by either `i64` or `&'static str`. Those values can
917/// be set using the `#[php(value = "value")]` or `#[php(value = 123)]`
918/// attributes on the enum variants.
919///
920/// All variants must have a value of the same type, either all `i64` or all
921/// `&'static str`.
922///
923/// A backed enum converts to and from its value. The macro implements
924/// `From<Enum>` for the value type, and `TryFrom<i64>` or `TryFrom<&str>` for
925/// the enum. `TryFrom` returns `Error::InvalidEnumCase` when no case has the
926/// value.
927///
928/// ```rust,no_run,ignore
929/// # #![cfg_attr(windows, feature(abi_vectorcall))]
930/// # extern crate ext_php_rs;
931/// use ext_php_rs::prelude::*;
932///
933/// #[php_enum]
934/// pub enum Suit {
935///     #[php(value = "hearts")]
936///     Hearts,
937///     #[php(value = "diamonds")]
938///     Diamonds,
939///     #[php(value = "clubs")]
940///     Clubs,
941///     #[php(value = "spades")]
942///     Spades,
943/// }
944/// #[php_module]
945/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
946///     module.enumeration::<Suit>()
947/// }
948/// # fn main() {}
949/// ```
950///
951/// ### 'Native' Discriminators
952/// Native rust discriminants are currently not supported and will not be
953/// exported to PHP.
954///
955/// To avoid confusion a compiler error will be raised if you try to use a
956/// native discriminant. You can ignore this error by adding the
957/// `#[php(allow_native_discriminants)]` attribute to your enum.
958///
959/// ```rust,no_run,ignore
960/// # #![cfg_attr(windows, feature(abi_vectorcall))]
961/// # extern crate ext_php_rs;
962/// use ext_php_rs::prelude::*;
963///
964/// #[php_enum]
965/// #[php(allow_native_discriminants)]
966/// pub enum Suit {
967///     Hearts = 1,
968///     Diamonds = 2,
969///     Clubs = 3,
970///     Spades = 4,
971/// }
972///
973/// #[php_module]
974/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
975///     module.enumeration::<Suit>()
976/// }
977/// # fn main() {}
978/// ```
979///
980///
981/// TODO: Add backed enums example
982// END DOCS FROM enum.md
983#[proc_macro_attribute]
984pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
985    php_enum_internal(args.into(), input.into()).into()
986}
987
988#[allow(clippy::needless_pass_by_value)]
989fn php_enum_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
990    let input = parse_macro_input2!(input as ItemEnum);
991    if let Err(e) = reject_args(&args, "php_enum", PHP_ATTR_HINT) {
992        return e.to_compile_error();
993    }
994
995    enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
996}
997
998// BEGIN DOCS FROM interface.md
999/// # `#[php_interface]` Attribute
1000///
1001/// You can export a `Trait` block to PHP. This exports all methods as well as
1002/// constants to PHP on the interface. Trait method SHOULD NOT contain default
1003/// implementations, as these are not supported in PHP interfaces.
1004///
1005/// ## Options
1006///
1007/// By default all constants are renamed to `UPPER_CASE` and all methods are
1008/// renamed to `camelCase`. This can be changed by passing the
1009/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
1010/// the `impl` block. The options are:
1011///
1012/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
1013///   case.
1014/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
1015///   snake case.
1016///
1017/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
1018/// for a list of all available cases.
1019///
1020/// The macro itself takes no arguments: `#[php_interface(name = "Foo")]` is a
1021/// compile error. Put the options in `#[php(...)]` on the trait.
1022///
1023/// ## Methods
1024///
1025/// A trait method accepts the `name`, `change_case`, `defaults`, `optional` and
1026/// `vis` options. Interfaces cannot declare property accessors or constructors,
1027/// so `getter`, `setter` and `constructor` are compile errors here. See
1028/// [`php_impl`](./impl.md#) for how each accepted option behaves.
1029///
1030/// ## Constants
1031///
1032/// A trait constant accepts the `name` and `change_case` options. Interface
1033/// constants are always public, so `vis` is a compile error here. See
1034/// [`php_impl`](./impl.md#constants) for the value types.
1035///
1036/// ## Example
1037///
1038/// Define an example trait with methods and constant:
1039///
1040/// ```rust,no_run,ignore
1041/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1042/// # extern crate ext_php_rs;
1043/// use ext_php_rs::{prelude::*, types::ZendClassObject};
1044///
1045///
1046/// #[php_interface]
1047/// #[php(name = "Rust\\TestInterface")]
1048/// trait Test {
1049///     const TEST: &'static str = "TEST";
1050///
1051///     fn co();
1052///
1053///     #[php(defaults(value = 0))]
1054///     fn set_value(&mut self, value: i32);
1055/// }
1056///
1057/// #[php_module]
1058/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1059///     module
1060///         .interface::<PhpInterfaceTest>()
1061/// }
1062///
1063/// # fn main() {}
1064/// ```
1065///
1066/// Using our newly created interface in PHP:
1067///
1068/// ```php
1069/// <?php
1070///
1071/// assert(interface_exists("Rust\TestInterface"));
1072///
1073/// class B implements Rust\TestInterface {
1074///
1075///     public static function co() {}
1076///
1077///     public function setValue(?int $value = 0) {
1078///
1079///     }
1080/// }
1081/// ```
1082///
1083/// ## Interface Inheritance
1084///
1085/// PHP interfaces can extend other interfaces. You can achieve this in two
1086/// ways:
1087///
1088/// ### Using `#[php(extends(...))]`
1089///
1090/// Use the `extends` attribute to extend a built-in PHP interface or another
1091/// Rust-defined interface. Generated stubs write each parent interface name as
1092/// a fully qualified name, with a leading `\`. A leading `\` in `stub = "..."`
1093/// is optional.
1094///
1095/// For built-in PHP interfaces, use the explicit form:
1096///
1097/// ```rust,no_run,ignore
1098/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1099/// # extern crate ext_php_rs;
1100/// use ext_php_rs::prelude::*;
1101/// use ext_php_rs::zend::ce;
1102///
1103/// #[php_interface]
1104/// #[php(extends(ce = ce::throwable, stub = "\\Throwable"))]
1105/// #[php(name = "MyException")]
1106/// trait MyExceptionInterface {
1107///     fn get_error_code(&self) -> i32;
1108/// }
1109///
1110/// # fn main() {}
1111/// ```
1112///
1113/// For Rust-defined interfaces, you can use the simpler type syntax:
1114///
1115/// ```rust,ignore
1116/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1117/// # extern crate ext_php_rs;
1118/// use ext_php_rs::prelude::*;
1119///
1120/// #[php_interface]
1121/// trait BaseInterface {
1122///     fn base_method(&self) -> i32;
1123/// }
1124///
1125/// #[php_interface]
1126/// #[php(extends(BaseInterface))]
1127/// trait ExtendedInterface {
1128///     fn extended_method(&self) -> String;
1129/// }
1130///
1131/// # fn main() {}
1132/// ```
1133///
1134/// ### Using Rust Trait Bounds
1135///
1136/// You can also use Rust's trait bound syntax. When a trait marked with
1137/// `#[php_interface]` has supertraits, the PHP interface will automatically
1138/// extend those parent interfaces:
1139///
1140/// ```rust,no_run,ignore
1141/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1142/// # extern crate ext_php_rs;
1143/// use ext_php_rs::prelude::*;
1144///
1145/// #[php_interface]
1146/// #[php(name = "Rust\\ParentInterface")]
1147/// trait ParentInterface {
1148///     fn parent_method(&self) -> String;
1149/// }
1150///
1151/// // ChildInterface extends ParentInterface in PHP
1152/// #[php_interface]
1153/// #[php(name = "Rust\\ChildInterface")]
1154/// trait ChildInterface: ParentInterface {
1155///     fn child_method(&self) -> String;
1156/// }
1157///
1158/// #[php_module]
1159/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1160///     module
1161///         .interface::<PhpInterfaceParentInterface>()
1162///         .interface::<PhpInterfaceChildInterface>()
1163/// }
1164///
1165/// # fn main() {}
1166/// ```
1167///
1168/// In PHP:
1169///
1170/// ```php
1171/// <?php
1172///
1173/// // ChildInterface extends ParentInterface
1174/// assert(is_a('Rust\ChildInterface', 'Rust\ParentInterface', true));
1175/// ```
1176///
1177/// # `#[php_impl_interface]` Attribute
1178///
1179/// The `#[php_impl_interface]` attribute allows a Rust class to implement a
1180/// custom PHP interface defined with `#[php_interface]`. This creates a
1181/// relationship where PHP's `instanceof` and `is_a()` recognize the
1182/// implementation.
1183///
1184/// **Key feature**: The macro automatically registers the trait methods as PHP
1185/// methods on the class. You don't need to duplicate them in a separate
1186/// `#[php_impl]` block.
1187///
1188/// ## Example
1189///
1190/// ```rust,no_run,ignore
1191/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1192/// # extern crate ext_php_rs;
1193/// use ext_php_rs::prelude::*;
1194///
1195/// // Define a custom interface
1196/// #[php_interface]
1197/// #[php(name = "Rust\\Greetable")]
1198/// trait Greetable {
1199///     fn greet(&self) -> String;
1200/// }
1201///
1202/// // Define a class
1203/// #[php_class]
1204/// #[php(name = "Rust\\Greeter")]
1205/// pub struct Greeter {
1206///     name: String,
1207/// }
1208///
1209/// #[php_impl]
1210/// impl Greeter {
1211///     pub fn __construct(name: String) -> Self {
1212///         Self { name }
1213///     }
1214///
1215///     // Note: No need to add greet() here - it's automatically
1216///     // registered by #[php_impl_interface] below
1217/// }
1218///
1219/// // Implement the interface for the class
1220/// // This automatically registers greet() as a PHP method
1221/// #[php_impl_interface]
1222/// impl Greetable for Greeter {
1223///     fn greet(&self) -> String {
1224///         format!("Hello, {}!", self.name)
1225///     }
1226/// }
1227///
1228/// #[php_module]
1229/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1230///     module
1231///         .interface::<PhpInterfaceGreetable>()
1232///         .class::<Greeter>()
1233/// }
1234///
1235/// # fn main() {}
1236/// ```
1237///
1238/// Using in PHP:
1239///
1240/// ```php
1241/// <?php
1242///
1243/// $greeter = new Rust\Greeter("World");
1244///
1245/// // instanceof works
1246/// assert($greeter instanceof Rust\Greetable);
1247///
1248/// // is_a() works
1249/// assert(is_a($greeter, 'Rust\Greetable'));
1250///
1251/// // The greet() method is available (registered by #[php_impl_interface])
1252/// echo $greeter->greet(); // Output: Hello, World!
1253///
1254/// // Can be used as type hint
1255/// function greet(Rust\Greetable $obj): void {
1256///     echo $obj->greet();
1257/// }
1258///
1259/// greet($greeter);
1260/// ```
1261///
1262/// ## When to Use
1263///
1264/// - Use `#[php_impl_interface]` for custom interfaces you define with
1265///   `#[php_interface]`
1266/// - Use `#[php(implements(ce = ...))]` on `#[php_class]` for built-in PHP
1267///   interfaces like `Iterator`, `ArrayAccess`, `Countable`, etc.
1268///
1269/// See the [Classes documentation](./classes.md#implementing-an-interface) for
1270/// examples of implementing built-in interfaces.
1271///
1272/// ## Cross-Crate Support
1273///
1274/// The `#[php_impl_interface]` macro supports cross-crate interface discovery
1275/// via the [`inventory`](https://crates.io/crates/inventory) crate. This means you can define
1276/// an interface in one crate and implement it in another crate, and the
1277/// implementation will be automatically discovered at link time.
1278///
1279/// ### Example: Defining an Interface in a Library Crate
1280///
1281/// First, create a library crate that defines the interface:
1282///
1283/// ```toml
1284/// # my-interfaces/Cargo.toml
1285/// [package]
1286/// name = "my-interfaces"
1287/// version = "0.1.0"
1288///
1289/// [dependencies]
1290/// ext-php-rs = "0.15"
1291/// ```
1292///
1293/// ```rust,no_run,ignore,ignore
1294/// // my-interfaces/src/lib.rs
1295/// use ext_php_rs::prelude::*;
1296///
1297/// /// A serializable interface that can convert objects to JSON.
1298/// #[php_interface]
1299/// #[php(name = "MyInterfaces\\Serializable")]
1300/// pub trait Serializable {
1301///     fn to_json(&self) -> String;
1302/// }
1303///
1304/// // Re-export the generated PHP interface struct for consumers
1305/// pub use PhpInterfaceSerializable;
1306/// ```
1307///
1308/// ### Example: Implementing the Interface in Another Crate
1309///
1310/// Now create your extension crate that implements the interface:
1311///
1312/// ```toml
1313/// # my-extension/Cargo.toml
1314/// [package]
1315/// name = "my-extension"
1316/// version = "0.1.0"
1317///
1318/// [lib]
1319/// crate-type = ["cdylib"]
1320///
1321/// [dependencies]
1322/// ext-php-rs = "0.15"
1323/// my-interfaces = { path = "../my-interfaces" }
1324/// ```
1325///
1326/// ```rust,no_run,ignore,ignore
1327/// // my-extension/src/lib.rs
1328/// use ext_php_rs::prelude::*;
1329/// use my_interfaces::Serializable;
1330///
1331/// #[php_class]
1332/// #[php(name = "MyExtension\\User")]
1333/// pub struct User {
1334///     name: String,
1335///     email: String,
1336/// }
1337///
1338/// #[php_impl]
1339/// impl User {
1340///     pub fn __construct(name: String, email: String) -> Self {
1341///         Self { name, email }
1342///     }
1343///
1344///     // Note: No need to add to_json() here - it's automatically
1345///     // registered by #[php_impl_interface] below
1346/// }
1347///
1348/// // Register the interface implementation
1349/// // This automatically registers to_json() as a PHP method
1350/// #[php_impl_interface]
1351/// impl Serializable for User {
1352///     fn to_json(&self) -> String {
1353///         format!(r#"{{"name":"{}","email":"{}"}}"#, self.name, self.email)
1354///     }
1355/// }
1356///
1357/// #[php_module]
1358/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1359///     module
1360///         // Register the interface from the library crate
1361///         .interface::<my_interfaces::PhpInterfaceSerializable>()
1362///         .class::<User>()
1363/// }
1364/// ```
1365///
1366/// ### Using in PHP
1367///
1368/// ```php
1369/// <?php
1370///
1371/// use MyExtension\User;
1372/// use MyInterfaces\Serializable;
1373///
1374/// $user = new User("John", "john@example.com");
1375///
1376/// // instanceof works across crates
1377/// assert($user instanceof Serializable);
1378///
1379/// // Type hints work
1380/// function serialize_object(Serializable $obj): string {
1381///     return $obj->toJson();
1382/// }
1383///
1384/// echo serialize_object($user);
1385/// // Output: {"name":"John","email":"john@example.com"}
1386/// ```
1387///
1388/// ### Important Notes
1389///
1390/// 1. **Automatic method registration**: The `#[php_impl_interface]` macro
1391///    automatically registers all trait methods as PHP methods on the class.
1392///    You don't need to duplicate them in a `#[php_impl]` block.
1393///
1394/// 2. **Interface registration**: The interface must be registered in the
1395///    `#[php_module]` function using `.interface::<PhpInterfaceName>()`.
1396///
1397/// 3. **Link-time discovery**: The `inventory` crate uses link-time
1398///    registration for interface discovery, so all implementations are
1399///    automatically discovered when the final binary is linked.
1400///
1401/// 4. **Method names**: Each method gets the PHP name that `#[php_interface]`
1402///    declared for it. The trait's `change_method_case` and the `name` or
1403///    `change_case` of a trait method apply to the class too.
1404///    `#[php_impl_interface]` takes no arguments.
1405// END DOCS FROM interface.md
1406#[proc_macro_attribute]
1407pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
1408    php_interface_internal(args.into(), input.into()).into()
1409}
1410
1411#[allow(clippy::needless_pass_by_value)]
1412fn php_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1413    let input = parse_macro_input2!(input as ItemTrait);
1414    if let Err(e) = reject_args(&args, "php_interface", PHP_ATTR_HINT) {
1415        return e.to_compile_error();
1416    }
1417
1418    interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
1419}
1420
1421// BEGIN DOCS FROM function.md
1422/// # `#[php_function]` Attribute
1423///
1424/// Used to annotate functions which should be exported to PHP. Note that this
1425/// should not be used on class methods - see the `#[php_impl]` macro for that.
1426///
1427/// See the [list of types](../types/index.md) that are valid as parameter and
1428/// return types.
1429///
1430/// A function accepts the `name`, `change_case`, `defaults` and `optional`
1431/// options. PHP functions have no visibility, so `#[php(vis = "...")]` is a
1432/// compile error on a function.
1433///
1434/// ## Optional parameters
1435///
1436/// An `Option<T>` parameter accepts `null` and can be omitted. The macro reads
1437/// this from the type through `FromZvalMut::NULLABLE`, so a type alias such as
1438/// `type MaybeAge = Option<i64>` or the qualified path
1439/// `std::option::Option<i64>` behaves like `Option<i64>`. The trailing run of
1440/// parameters that are nullable or have a default value is optional.
1441///
1442/// ```rust,no_run,ignore
1443/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1444/// # extern crate ext_php_rs;
1445/// use ext_php_rs::prelude::*;
1446///
1447/// #[php_function]
1448/// pub fn greet(name: String, age: Option<i32>) -> String {
1449///     let mut greeting = format!("Hello, {}!", name);
1450///
1451///     if let Some(age) = age {
1452///         greeting += &format!(" You are {} years old.", age);
1453///     }
1454///
1455///     greeting
1456/// }
1457///
1458/// #[php_module]
1459/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1460///     module.function(wrap_function!(greet))
1461/// }
1462/// # fn main() {}
1463/// ```
1464///
1465/// Default parameter values can also be set for optional parameters. This is
1466/// done through the `#[php(defaults)]` attribute option. When an optional
1467/// parameter has a default, it does not need to be a variant of `Option`. The
1468/// default expression is converted into the parameter type with `Into`, and the
1469/// type must implement `StubLiteral`, which every scalar, string, `Option`,
1470/// `Vec` and `HashMap` does. The module renders the value once at load time
1471/// into the stub file and into `ReflectionParameter::getDefaultValue()`. Each
1472/// key in `defaults` must name a parameter of the function. If a key names no
1473/// parameter, the build fails:
1474///
1475/// ```rust,no_run,ignore
1476/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1477/// # extern crate ext_php_rs;
1478/// use ext_php_rs::prelude::*;
1479///
1480/// #[php_function]
1481/// #[php(defaults(offset = 0))]
1482/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
1483///     let haystack: String = haystack.chars().skip(offset as usize).collect();
1484///     haystack.find(needle)
1485/// }
1486///
1487/// #[php_module]
1488/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1489///     module.function(wrap_function!(rusty_strpos))
1490/// }
1491/// # fn main() {}
1492/// ```
1493///
1494/// Note that if there is a non-optional argument after an argument that is a
1495/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
1496/// argument rather than an optional argument.
1497///
1498/// ```rust,no_run,ignore
1499/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1500/// # extern crate ext_php_rs;
1501/// use ext_php_rs::prelude::*;
1502///
1503/// /// `age` will be deemed required and nullable rather than optional.
1504/// #[php_function]
1505/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
1506///     let mut greeting = format!("Hello, {}!", name);
1507///
1508///     if let Some(age) = age {
1509///         greeting += &format!(" You are {} years old.", age);
1510///     }
1511///
1512///     greeting += &format!(" {}.", description);
1513///     greeting
1514/// }
1515///
1516/// #[php_module]
1517/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1518///     module.function(wrap_function!(greet))
1519/// }
1520/// # fn main() {}
1521/// ```
1522///
1523/// You can also specify the optional arguments if you want to have nullable
1524/// arguments before optional arguments. This is done through the `optional`
1525/// attribute option, which names the first optional parameter. PHP callers can
1526/// omit that parameter and every parameter after it. Each of these parameters
1527/// must be an `Option<T>`, have a default, or be the variadic `&[T]` tail.
1528/// Otherwise the build fails with an error on the type of that parameter. If
1529/// `optional` names no parameter, the build also fails:
1530///
1531/// ```rust,no_run,ignore
1532/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1533/// # extern crate ext_php_rs;
1534/// use ext_php_rs::prelude::*;
1535///
1536/// /// `age` will be deemed required and nullable rather than optional,
1537/// /// while description will be optional.
1538/// #[php_function]
1539/// #[php(optional = "description")]
1540/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
1541///     let mut greeting = format!("Hello, {}!", name);
1542///
1543///     if let Some(age) = age {
1544///         greeting += &format!(" You are {} years old.", age);
1545///     }
1546///
1547///     if let Some(description) = description {
1548///         greeting += &format!(" {}.", description);
1549///     }
1550///
1551///     greeting
1552/// }
1553///
1554/// #[php_module]
1555/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1556///     module.function(wrap_function!(greet))
1557/// }
1558/// # fn main() {}
1559/// ```
1560///
1561/// ## Variadic Functions
1562///
1563/// A slice parameter `&[T]` in last position is variadic, the equivalent of the
1564/// PHP `...$args` syntax. `T` is any argument type: `&[&Zval]` receives the raw
1565/// values, `&[i64]` receives converted integers. A variadic parameter that is
1566/// not the last parameter is a compile error. The slice must be spelled in the
1567/// signature; a type alias that hides the slice is not detected.
1568///
1569/// ```rust,no_run,ignore
1570/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1571/// # extern crate ext_php_rs;
1572/// use ext_php_rs::{prelude::*, types::Zval};
1573///
1574/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
1575/// #[php_function]
1576/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
1577///     // numbers is a slice of 4 Zvals all of type long
1578///     number
1579/// }
1580///
1581/// #[php_module]
1582/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1583///     module.function(wrap_function!(add))
1584/// }
1585/// # fn main() {}
1586/// ```
1587///
1588/// ## Performance
1589///
1590/// The `#[php_function]` macro generates a zero-allocation fast path that reads
1591/// arguments directly from the PHP call frame, matching how native C extensions
1592/// parse parameters. This applies automatically — no configuration needed.
1593///
1594/// The same fast path is used for class methods defined with `#[php_impl]`,
1595/// including instance methods (`&self`, `&mut self`) and static methods.
1596///
1597/// ```rust,no_run,ignore
1598/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1599/// # extern crate ext_php_rs;
1600/// # use ext_php_rs::prelude::*;
1601/// // Fast path: standalone functions
1602/// #[php_function]
1603/// pub fn fast(name: String, age: Option<i32>) -> String { name }
1604///
1605/// // Fast path: parameters with defaults
1606/// #[php_function]
1607/// #[php(defaults(offset = 0))]
1608/// pub fn also_fast(haystack: &str, offset: i64) -> i64 { offset }
1609/// # fn main() {}
1610/// ```
1611///
1612/// ```rust,ignore
1613/// #[php_impl]
1614/// impl MyClass {
1615///     // Fast path: static methods
1616///     pub fn create(n: i32) -> Self { /* ... */ }
1617///
1618///     // Fast path: instance methods
1619///     pub fn get_value(&self, offset: i32) -> i32 { /* ... */ }
1620/// }
1621/// ```
1622///
1623/// The only case that falls back to the runtime argument parser is when using
1624/// variadic arguments (`&[T]`, the Rust equivalent of PHP's `...$args`):
1625///
1626/// ```rust,no_run,ignore
1627/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1628/// # extern crate ext_php_rs;
1629/// # use ext_php_rs::{prelude::*, types::Zval};
1630/// // Slower path: variadic arguments require runtime parsing
1631/// #[php_function]
1632/// pub fn add(first: u32, rest: &[&Zval]) -> u32 { first }
1633/// # fn main() {}
1634/// ```
1635///
1636/// ## Returning `Result<T, E>`
1637///
1638/// You can also return a `Result` from the function. The error variant will be
1639/// translated into an exception and thrown. See the section on
1640/// [exceptions](../exceptions.md) for more details.
1641// END DOCS FROM function.md
1642#[proc_macro_attribute]
1643pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
1644    php_function_internal(args.into(), input.into()).into()
1645}
1646
1647#[allow(clippy::needless_pass_by_value)]
1648fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1649    let input = parse_macro_input2!(input as ItemFn);
1650    if let Err(e) = reject_args(&args, "php_function", PHP_ATTR_HINT) {
1651        return e.to_compile_error();
1652    }
1653
1654    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
1655}
1656
1657// BEGIN DOCS FROM constant.md
1658/// # `#[php_const]` Attribute
1659///
1660/// Exports a Rust constant as a global PHP constant. The constant can be any
1661/// type that implements `IntoConst`.
1662///
1663/// The `wrap_constant!()` macro can be used to simplify the registration of
1664/// constants. It sets the name and doc comments for the constant.
1665///
1666/// You can rename the const with options:
1667///
1668/// - `name` - Allows you to rename the property, e.g. `#[php(name =
1669///   "new_name")]`
1670/// - `change_case` - Allows you to rename the property using rename rules, e.g.
1671///   `#[php(change_case = "PascalCase")]`
1672///
1673/// A global constant has no visibility. `#[php(vis = "...")]` on a
1674/// `#[php_const]` is a compile error. Use `vis` on constants inside a
1675/// `#[php_impl]` block.
1676///
1677/// ## Examples
1678///
1679/// ```rust,no_run,ignore
1680/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1681/// # extern crate ext_php_rs;
1682/// use ext_php_rs::prelude::*;
1683///
1684/// #[php_const]
1685/// const TEST_CONSTANT: i32 = 100;
1686///
1687/// #[php_const]
1688/// #[php(name = "I_AM_RENAMED")]
1689/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
1690///
1691/// #[php_const]
1692/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
1693///
1694/// #[php_module]
1695/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1696///     module
1697///         .constant(wrap_constant!(TEST_CONSTANT))
1698///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
1699///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
1700/// }
1701/// # fn main() {}
1702/// ```
1703///
1704/// ## PHP usage
1705///
1706/// ```php
1707/// <?php
1708///
1709/// var_dump(TEST_CONSTANT); // int(100)
1710/// var_dump(I_AM_RENAMED); // int(42)
1711/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
1712/// ```
1713// END DOCS FROM constant.md
1714#[proc_macro_attribute]
1715pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
1716    php_const_internal(args.into(), input.into()).into()
1717}
1718
1719#[allow(clippy::needless_pass_by_value)]
1720fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1721    let input = parse_macro_input2!(input as ItemConst);
1722    if let Err(e) = reject_args(&args, "php_const", PHP_ATTR_HINT) {
1723        return e.to_compile_error();
1724    }
1725
1726    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
1727}
1728
1729// BEGIN DOCS FROM module.md
1730/// # `#[php_module]` Attribute
1731///
1732/// The module macro is used to annotate the `get_module` function, which is
1733/// used by the PHP interpreter to retrieve information about your extension,
1734/// including the name, version, functions and extra initialization functions.
1735/// Regardless if you use this macro, your extension requires a `extern "C" fn
1736/// get_module()` so that PHP can get this information.
1737///
1738/// The function is renamed to `get_module` if you have used another name. The
1739/// macro also exports a crate-prefixed `<crate_name>_get_module` alias, used
1740/// when [statically linking the extension into
1741/// php-src](../advanced/static_linking.md). Building with
1742/// `EXT_PHP_RS_STATIC_EXT=1` removes the unmangled `get_module` export (the
1743/// function itself stays callable from Rust) so a statically linked
1744/// extension cannot collide with another extension exporting `get_module`.
1745/// Dynamic builds (`extension=` / `dl()`) must not set the variable.
1746///
1747/// The function is passed an instance of `ModuleBuilder` which allows you to
1748/// register the following (if required):
1749///
1750/// - Functions, classes, and constants
1751/// - Extension and request startup and shutdown functions.
1752///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
1753/// - PHP extension information function
1754///   - Used by the `phpinfo()` function to get information about your
1755///     extension.
1756///
1757/// Classes and constants are not registered with PHP in the `get_module`
1758/// function. These are registered inside the extension startup function.
1759///
1760/// ## Startup failures
1761///
1762/// If a constant, an interface, a class or an enum cannot be registered, the
1763/// generated startup function logs the cause as an `E_CORE_WARNING` and returns
1764/// `FAILURE`. PHP then reports `Unable to start <extension> module`. At engine
1765/// startup PHP exits. From `dl()` the request fails. A panic during
1766/// registration gets the same treatment. The process does not abort in either
1767/// case.
1768///
1769/// `get_module` cannot report a failure, because PHP reads the returned entry
1770/// without a check. If the `ModuleBuilder` cannot become a module entry, for
1771/// example because a function name, an argument name or the module name
1772/// contains a NUL byte, the macro returns a placeholder entry with the name of
1773/// the crate. The startup function of that entry logs the build error and fails
1774/// as described above.
1775///
1776/// The `startup` function that you name in `#[php_module(startup = ...)]` is
1777/// your own `extern "C"` function. A panic inside it is not caught.
1778///
1779/// ## Usage
1780///
1781/// ```rust,no_run,ignore
1782/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1783/// # extern crate ext_php_rs;
1784/// use ext_php_rs::{
1785///     prelude::*,
1786///     zend::ModuleEntry,
1787///     info_table_start,
1788///     info_table_row,
1789///     info_table_end
1790/// };
1791///
1792/// #[php_const]
1793/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
1794///
1795/// #[php_class]
1796/// pub struct Test {
1797///     a: i32,
1798///     b: i32
1799/// }
1800/// #[php_function]
1801/// pub fn hello_world() -> &'static str {
1802///     "Hello, world!"
1803/// }
1804///
1805/// /// Used by the `phpinfo()` function and when you run `php -i`.
1806/// /// This will probably be simplified with another macro eventually!
1807/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
1808///     info_table_start!();
1809///     info_table_row!("my extension", "enabled");
1810///     info_table_end!();
1811/// }
1812///
1813/// #[php_module]
1814/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1815///     module
1816///         .constant(wrap_constant!(MY_CUSTOM_CONST))
1817///         .class::<Test>()
1818///         .function(wrap_function!(hello_world))
1819///         .info_function(php_module_info)
1820/// }
1821/// # fn main() {}
1822/// ```
1823// END DOCS FROM module.md
1824#[proc_macro_attribute]
1825pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
1826    php_module_internal(args.into(), input.into()).into()
1827}
1828
1829#[allow(clippy::needless_pass_by_value)]
1830fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1831    let input = parse_macro_input2!(input as ItemFn);
1832    if let Err(e) = reject_args(&args, "php_module", PHP_ATTR_HINT) {
1833        return e.to_compile_error();
1834    }
1835
1836    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
1837}
1838
1839// BEGIN DOCS FROM impl.md
1840/// # `#[php_impl]` Attribute
1841///
1842/// You can export an entire `impl` block to PHP. This exports all methods as
1843/// well as constants to PHP on the class that it is implemented on. This
1844/// requires the `#[php_class]` macro to already be used on the underlying
1845/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
1846/// block can be exported per class.
1847///
1848/// If you do not want a function exported to PHP, you should place it in a
1849/// separate `impl` block.
1850///
1851/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
1852/// &raquo;](./async_impl.md) for more info.
1853///
1854/// ## Options
1855///
1856/// By default all constants are renamed to `UPPER_CASE` and all methods are
1857/// renamed to camelCase. This can be changed by passing the
1858/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
1859/// the `impl` block. The options are:
1860///
1861/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
1862///   case.
1863/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
1864///   snake case.
1865///
1866/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
1867/// for a list of all available cases.
1868///
1869/// ## Methods
1870///
1871/// Methods basically follow the same rules as functions, so read about the
1872/// [`php_function`] macro first. The primary difference between functions and
1873/// methods is they are bounded by their class object.
1874///
1875/// Both instance and static methods benefit from the same zero-allocation fast
1876/// path as standalone functions. See the
1877/// [Performance](./function.md#performance) section for details.
1878///
1879/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
1880/// a consuming `self` parameter. Static methods can omit this `self` parameter.
1881///
1882/// To access the underlying Zend object, you can take a reference to a
1883/// `ZendClassObject<T>` in place of the self parameter, where the parameter
1884/// must be named `self_`. This can also be used to return a reference to
1885/// `$this`.
1886///
1887/// The rest of the options are passed as separate attributes:
1888///
1889/// - `#[php(defaults(i = 5, b = "\"hello\""))]` - Sets the default value for
1890///   parameter(s). Each key must name a parameter.
1891/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
1892///   also sets the remaining parameters as optional, so each of them must be an
1893///   `Option<T>` or have a default. Otherwise the build fails.
1894/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
1895///   "private")]` - Sets the visibility of the method.
1896/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
1897///   identifier, without renaming the Rust method name.
1898/// - `#[php(final)]` - Makes the method final (cannot be overridden in
1899///   subclasses).
1900/// - `#[php(abstract)]` - Makes the method abstract (must be implemented by
1901///   subclasses). Can only be used in abstract classes.
1902///
1903/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
1904/// the equivalent function attribute parameters. A `self_` parameter is the PHP
1905/// object, not a PHP parameter, so `defaults` and `optional` cannot name it. A
1906/// `getter` or `setter` method accepts neither option.
1907///
1908/// ### Static Methods
1909///
1910/// Methods that do not take a `&self` or `&mut self` parameter are
1911/// automatically exported as static methods. These can be called on the class
1912/// itself without creating an instance.
1913///
1914/// ```rust,ignore
1915/// #[php_impl]
1916/// impl MyClass {
1917///     // Static method - no self parameter
1918///     pub fn create_default() -> Self {
1919///         Self { /* ... */ }
1920///     }
1921///
1922///     // Instance method - takes &self
1923///     pub fn get_value(&self) -> i32 {
1924///         self.value
1925///     }
1926/// }
1927/// ```
1928///
1929/// From PHP:
1930///
1931/// ```php
1932/// $obj = MyClass::createDefault(); // Static call
1933/// $val = $obj->getValue();         // Instance call
1934/// ```
1935///
1936/// ### Final Methods
1937///
1938/// Methods marked with `#[php(final)]` cannot be overridden in subclasses. This
1939/// is useful when you want to prevent modification of critical functionality.
1940///
1941/// ```rust,ignore
1942/// use ext_php_rs::prelude::*;
1943///
1944/// #[php_class]
1945/// pub struct SecureClass;
1946///
1947/// #[php_impl]
1948/// impl SecureClass {
1949///     #[php(final)]
1950///     pub fn secure_method(&self) -> &str {
1951///         "This cannot be overridden"
1952///     }
1953///
1954///     // Final static methods are also supported
1955///     #[php(final)]
1956///     pub fn secure_static() -> i32 {
1957///         42
1958///     }
1959/// }
1960/// ```
1961///
1962/// ### Abstract Methods
1963///
1964/// Methods marked with `#[php(abstract)]` must be implemented by subclasses.
1965/// Abstract methods can only be defined in abstract classes (classes with
1966/// `ClassFlags::Abstract`).
1967///
1968/// ```rust,ignore
1969/// use ext_php_rs::prelude::*;
1970/// use ext_php_rs::flags::ClassFlags;
1971///
1972/// #[php_class]
1973/// #[php(flags = ClassFlags::Abstract)]
1974/// pub struct AbstractShape;
1975///
1976/// #[php_impl]
1977/// impl AbstractShape {
1978///     // Protected constructor for subclasses
1979///     #[php(vis = "protected")]
1980///     pub fn __construct() -> Self {
1981///         Self
1982///     }
1983///
1984///     // Abstract method - subclasses must implement this.
1985///     // The body is never called; use unimplemented!() as a placeholder.
1986///     #[php(abstract)]
1987///     pub fn area(&self) -> f64 {
1988///         unimplemented!()
1989///     }
1990///
1991///     // Concrete method in abstract class
1992///     pub fn describe(&self) -> String {
1993///         format!("A shape with area {}", self.area())
1994///     }
1995/// }
1996/// ```
1997///
1998/// **Note:** Abstract method bodies are never called - they exist only because
1999/// Rust syntax requires a body for methods in `impl` blocks. Use
2000/// `unimplemented!()` as a clear placeholder.
2001///
2002/// **Note:** If you try to use `#[php(abstract)]` on a method in a non-abstract
2003/// class, you will get a compile-time error.
2004///
2005/// **Note:** PHP does not support abstract static methods. If you need static
2006/// behavior that can be customized by subclasses, use a regular instance method
2007/// or the [late static binding](https://www.php.net/manual/en/language.oop5.late-static-bindings.php)
2008/// pattern in PHP.
2009///
2010/// ### Constructors
2011///
2012/// By default, if a class does not have a constructor, it is not constructable
2013/// from PHP. It can only be returned from a Rust function to PHP.
2014///
2015/// Constructors are Rust methods which can take any amount of parameters and
2016/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
2017/// When the error variant of `Result` is encountered, it is thrown as an
2018/// exception and the class is not constructed.
2019///
2020/// Constructors are designated by either naming the method `__construct` or by
2021/// annotating a method with the `#[php(constructor)]` attribute. Note that when
2022/// using the attribute, the function is not exported to PHP like a regular
2023/// method.
2024///
2025/// Constructors cannot use the visibility or rename attributes listed above.
2026///
2027/// ## Constants
2028///
2029/// Constants are defined as regular Rust `impl` constants. Any type that
2030/// implements `IntoZval` can be used as a constant. A constant accepts the
2031/// `name`, `change_case` and `vis` options. The default visibility is `public`.
2032///
2033/// ```rust,no_run,ignore
2034/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2035/// # extern crate ext_php_rs;
2036/// # use ext_php_rs::prelude::*;
2037/// #[php_class]
2038/// pub struct Limits;
2039///
2040/// #[php_impl]
2041/// impl Limits {
2042///     const MAX_USERS: i64 = 100;
2043///
2044///     #[php(vis = "protected")]
2045///     const MAX_RETRIES: i64 = 3;
2046///
2047///     #[php(vis = "private", name = "SEED")]
2048///     const RANDOM_SEED: i64 = 42;
2049/// }
2050/// # fn main() {}
2051/// ```
2052///
2053/// PHP sees `public const MAX_USERS`, `protected const MAX_RETRIES` and
2054/// `private const SEED`. Reflection reports the same visibility, and the
2055/// generated stubs print it.
2056///
2057/// ## Property getters and setters
2058///
2059/// A class can expose a property whose value comes from Rust methods. Mark the
2060/// method that reads the value with `#[php(getter)]` and the method that writes
2061/// it with `#[php(setter)]`. The property name is the method name without its
2062/// `get_` or `set_` prefix, converted to camelCase. This is the same rule as
2063/// for `#[php(prop)]` fields: `get_first_name` gives the property `firstName`.
2064/// Properties are declared in the order in which their first
2065/// getter or setter appears in the `impl` block, after the `#[php(prop)]` field
2066/// properties of the struct.
2067///
2068/// If you want a different property name, pass a `name` or `change_case` option
2069/// to the `#[php]` attribute: `#[php(getter, name = "first_name")]` or
2070/// `#[php(getter, change_case = "snake_case")]`.
2071///
2072/// A property does not need both methods. A property with only a getter is
2073/// read-only. A property with only a setter is write-only.
2074///
2075/// One method cannot be both a getter and a setter. A property cannot have two
2076/// getters or two setters. The getter and the setter of one property must have
2077/// the same visibility. Each of these cases is a compile error.
2078///
2079/// A method property cannot have the same name as a `#[php(prop)]` field of the
2080/// struct. The two macros are expanded separately, so this is not a compile
2081/// error. The extension fails to load with this error:
2082///
2083/// ```text
2084/// property `Book::$title` is declared twice
2085/// ```
2086///
2087/// The PHP type of the property comes from the return type of the getter. If
2088/// the property has no getter, the type comes from the argument of the setter.
2089/// The documentation of the property comes from the getter, or from the setter
2090/// when the getter has none. Like field properties, the type must implement
2091/// `IntoZval` and `FromZval`.
2092///
2093/// ## Example
2094///
2095/// Continuing on from our `Human` example in the structs section, we will
2096/// define a constructor, as well as getters for the properties. We will also
2097/// define a constant for the maximum age of a `Human`.
2098///
2099/// ```rust,no_run,ignore
2100/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2101/// # extern crate ext_php_rs;
2102/// use ext_php_rs::{prelude::*, types::ZendClassObject};
2103///
2104/// #[php_class]
2105/// #[derive(Debug, Default)]
2106/// pub struct Human {
2107///     name: String,
2108///     age: i32,
2109///     #[php(prop)]
2110///     address: String,
2111/// }
2112///
2113/// #[php_impl]
2114/// impl Human {
2115///     const MAX_AGE: i32 = 100;
2116///
2117///     // No `#[constructor]` attribute required here - the name is `__construct`.
2118///     pub fn __construct(name: String, age: i32) -> Self {
2119///         Self {
2120///             name,
2121///             age,
2122///             address: String::new()
2123///         }
2124///     }
2125///
2126///     #[php(getter)]
2127///     pub fn get_name(&self) -> String {
2128///         self.name.to_string()
2129///     }
2130///
2131///     #[php(setter)]
2132///     pub fn set_name(&mut self, name: String) {
2133///         self.name = name;
2134///     }
2135///
2136///     #[php(getter)]
2137///     pub fn get_age(&self) -> i32 {
2138///         self.age
2139///     }
2140///
2141///     pub fn introduce(&self) {
2142///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
2143///     }
2144///
2145///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
2146///         dbg!(self_)
2147///     }
2148///
2149///     pub fn get_max_age() -> i32 {
2150///         Self::MAX_AGE
2151///     }
2152/// }
2153/// #[php_module]
2154/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2155///     module.class::<Human>()
2156/// }
2157/// # fn main() {}
2158/// ```
2159///
2160/// Using our newly created class in PHP:
2161///
2162/// ```php
2163/// <?php
2164///
2165/// $me = new Human('David', 20);
2166///
2167/// $me->introduce(); // My name is David and I am 20 years old.
2168/// var_dump(Human::get_max_age()); // int(100)
2169/// var_dump(Human::MAX_AGE); // int(100)
2170/// ```
2171///
2172/// [`php_async_impl`]: ./async_impl.md
2173// END DOCS FROM impl.md
2174#[proc_macro_attribute]
2175pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
2176    php_impl_internal(args.into(), input.into()).into()
2177}
2178
2179#[allow(clippy::needless_pass_by_value)]
2180fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2181    let input = parse_macro_input2!(input as ItemImpl);
2182    if let Err(e) = reject_args(&args, "php_impl", PHP_ATTR_HINT) {
2183        return e.to_compile_error();
2184    }
2185
2186    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2187}
2188
2189/// # `#[php_impl_interface]` Attribute
2190///
2191/// Marks a trait implementation as implementing a PHP interface. This allows
2192/// Rust structs marked with `#[php_class]` to implement Rust traits marked
2193/// with `#[php_interface]`, and have PHP recognize the relationship.
2194///
2195/// **Key feature**: The macro automatically registers the trait methods as PHP
2196/// methods on the class. You don't need to duplicate them in a separate
2197/// `#[php_impl]` block.
2198///
2199/// ## Usage
2200///
2201/// ```rust,no_run,ignore
2202/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2203/// # extern crate ext_php_rs;
2204/// use ext_php_rs::prelude::*;
2205///
2206/// #[php_interface]
2207/// trait MyInterface {
2208///     fn my_method(&self) -> String;
2209/// }
2210///
2211/// #[php_class]
2212/// struct MyClass;
2213///
2214/// // The trait method my_method() is automatically registered as a PHP method
2215/// #[php_impl_interface]
2216/// impl MyInterface for MyClass {
2217///     fn my_method(&self) -> String {
2218///         "Hello from MyClass!".to_string()
2219///     }
2220/// }
2221///
2222/// #[php_module]
2223/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2224///     module
2225///         .interface::<PhpInterfaceMyInterface>()
2226///         .class::<MyClass>()
2227/// }
2228/// # fn main() {}
2229/// ```
2230///
2231/// After registration, PHP's `is_a($obj, 'MyInterface')` will return `true`
2232/// for instances of `MyClass`, and `$obj->myMethod()` will be callable.
2233///
2234/// ## Method names
2235///
2236/// Each method is registered under the name that `#[php_interface]` gave it.
2237/// The trait's `change_method_case` and the `name` or `change_case` of a trait
2238/// method apply to the class too. The macro takes no arguments:
2239/// `#[php_impl_interface(change_method_case = "...")]` is a compile error.
2240///
2241/// ## Requirements
2242///
2243/// - The trait must be marked with `#[php_interface]`
2244/// - The struct must be marked with `#[php_class]`
2245/// - The interface must be registered before the class in the module builder
2246#[proc_macro_attribute]
2247pub fn php_impl_interface(args: TokenStream, input: TokenStream) -> TokenStream {
2248    php_impl_interface_internal(args.into(), input.into()).into()
2249}
2250
2251#[allow(clippy::needless_pass_by_value)]
2252fn php_impl_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2253    let input = parse_macro_input2!(input as ItemImpl);
2254    let attr_args = match darling::ast::NestedMeta::parse_meta_list(args) {
2255        Ok(v) => v,
2256        Err(e) => return e.to_compile_error(),
2257    };
2258    let args = match impl_interface::PhpImplInterfaceArgs::from_list(&attr_args) {
2259        Ok(v) => v,
2260        Err(e) => return e.write_errors(),
2261    };
2262
2263    impl_interface::parser(&args, &input).unwrap_or_else(|e| e.to_compile_error())
2264}
2265
2266// BEGIN DOCS FROM extern.md
2267/// # `#[php_extern]` Attribute
2268///
2269/// Attribute used to annotate `extern` blocks which are deemed as PHP
2270/// functions.
2271///
2272/// This allows you to 'import' PHP functions into Rust so that they can be
2273/// called like regular Rust functions. Parameters can be any type that
2274/// implements [`IntoZval`], and the return type can be anything that implements
2275/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
2276/// case).
2277///
2278/// Unlike most other attributes, this does not need to be placed inside a
2279/// `#[php_module]` block.
2280///
2281/// # Panics
2282///
2283/// The function can panic when called under a few circumstances:
2284///
2285/// * The function could not be found or was not callable.
2286/// * One of the parameters could not be converted into a [`Zval`].
2287/// * The actual function call failed internally.
2288/// * The output [`Zval`] could not be parsed into the output type.
2289///
2290/// Inside a function that PHP calls, these panics become a PHP `Error`. The
2291/// process does not abort. See [Exceptions](../exceptions.md#panics).
2292///
2293/// The last point can be important when interacting with functions that return
2294/// unions, such as [`strpos`] which can return an integer or a boolean. In this
2295/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
2296/// invalid, and vice versa.
2297///
2298/// # Example
2299///
2300/// This `extern` block imports the [`strpos`] function from PHP. Notice that
2301/// the string parameters can take either [`String`] or [`&str`], the optional
2302/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
2303/// as the return type is an integer-boolean union.
2304///
2305/// ```rust,no_run,ignore
2306/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2307/// # extern crate ext_php_rs;
2308/// use ext_php_rs::{
2309///     prelude::*,
2310///     types::Zval,
2311/// };
2312///
2313/// #[php_extern]
2314/// extern "C" {
2315///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
2316/// }
2317///
2318/// #[php_function]
2319/// pub fn my_strpos() {
2320///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
2321/// }
2322///
2323/// #[php_module]
2324/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
2325///     module.function(wrap_function!(my_strpos))
2326/// }
2327/// # fn main() {}
2328/// ```
2329///
2330/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
2331/// [`IntoZval`]: crate::convert::IntoZval
2332/// [`Zval`]: crate::types::Zval
2333// END DOCS FROM extern.md
2334#[proc_macro_attribute]
2335pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
2336    php_extern_internal(args.into(), input.into()).into()
2337}
2338
2339#[allow(clippy::needless_pass_by_value)]
2340fn php_extern_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2341    let input = parse_macro_input2!(input as ItemForeignMod);
2342    if let Err(e) = reject_args(&args, "php_extern", NO_ARGS_HINT) {
2343        return e.to_compile_error();
2344    }
2345
2346    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2347}
2348
2349// BEGIN DOCS FROM zval_convert.md
2350/// # `ZvalConvert` Derive Macro
2351///
2352/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
2353/// traits on a struct or enum.
2354///
2355/// ## Structs
2356///
2357/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
2358/// also implemented, mapping fields to properties in both directions. All
2359/// fields on the struct must implement `FromZval` as well. Generics are allowed
2360/// on structs that use the derive macro, however, the implementation will add a
2361/// `FromZval` bound to all generics types.
2362///
2363/// ### Examples
2364///
2365/// ```rust,no_run,ignore
2366/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2367/// # extern crate ext_php_rs;
2368/// use ext_php_rs::prelude::*;
2369///
2370/// #[derive(ZvalConvert)]
2371/// pub struct ExampleClass<'a> {
2372///     a: i32,
2373///     b: String,
2374///     c: &'a str
2375/// }
2376///
2377/// #[php_function]
2378/// pub fn take_object(obj: ExampleClass) {
2379///     dbg!(obj.a, obj.b, obj.c);
2380/// }
2381///
2382/// #[php_function]
2383/// pub fn give_object() -> ExampleClass<'static> {
2384///     ExampleClass {
2385///         a: 5,
2386///         b: "String".to_string(),
2387///         c: "Borrowed",
2388///     }
2389/// }
2390///
2391/// #[php_module]
2392/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2393///     module
2394///         .function(wrap_function!(take_object))
2395///         .function(wrap_function!(give_object))
2396/// }
2397/// # fn main() {}
2398/// ```
2399///
2400/// Calling from PHP:
2401///
2402/// ```php
2403/// <?php
2404///
2405/// $obj = new stdClass;
2406/// $obj->a = 5;
2407/// $obj->b = 'Hello, world!';
2408/// $obj->c = 'another string';
2409///
2410/// take_object($obj);
2411/// var_dump(give_object());
2412/// ```
2413///
2414/// Another example involving generics:
2415///
2416/// ```rust,no_run,ignore
2417/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2418/// # extern crate ext_php_rs;
2419/// use ext_php_rs::prelude::*;
2420///
2421/// // T must implement both `PartialEq<i32>` and `FromZval`.
2422/// #[derive(Debug, ZvalConvert)]
2423/// pub struct CompareVals<T: PartialEq<i32>> {
2424///     a: T,
2425///     b: T
2426/// }
2427///
2428/// #[php_function]
2429/// pub fn take_object(obj: CompareVals<i32>) {
2430///     dbg!(obj);
2431/// }
2432///
2433/// #[php_module]
2434/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2435///     module
2436///         .function(wrap_function!(take_object))
2437/// }
2438/// # fn main() {}
2439/// ```
2440///
2441/// ## Enums
2442///
2443/// When used on an enum, the `FromZval` implementation will treat the enum as a
2444/// tagged union with a mixed datatype. This allows you to accept multiple types
2445/// in a parameter, for example, a string and an integer.
2446///
2447/// The enum variants must not have named fields, and each variant must have
2448/// exactly one field (the type to extract from the zval). Optionally, the enum
2449/// may have one default variant with no data contained, which will be used when
2450/// the rest of the variants could not be extracted from the zval.
2451///
2452/// The ordering of the variants in the enum is important, as the `FromZval`
2453/// implementation will attempt to parse the zval data in order. For example, if
2454/// you put a `String` variant before an integer variant, the integer would be
2455/// converted to a string and passed as the string variant.
2456///
2457/// ### Examples
2458///
2459/// Basic example showing the importance of variant ordering and default field:
2460///
2461/// ```rust,no_run,ignore
2462/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2463/// # extern crate ext_php_rs;
2464/// use ext_php_rs::prelude::*;
2465///
2466/// #[derive(Debug, ZvalConvert)]
2467/// pub enum UnionExample<'a> {
2468///     Long(u64), // Long
2469///     ProperStr(&'a str), // Actual string - not a converted value
2470///     ParsedStr(String), // Potentially parsed string, i.e. a double
2471///     None // Zval did not contain anything that could be parsed above
2472/// }
2473///
2474/// #[php_function]
2475/// pub fn test_union(val: UnionExample) {
2476///     dbg!(val);
2477/// }
2478///
2479/// #[php_function]
2480/// pub fn give_union() -> UnionExample<'static> {
2481///     UnionExample::Long(5)
2482/// }
2483///
2484/// #[php_module]
2485/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2486///     module
2487///         .function(wrap_function!(test_union))
2488///         .function(wrap_function!(give_union))
2489/// }
2490/// # fn main() {}
2491/// ```
2492///
2493/// Use in PHP:
2494///
2495/// ```php
2496/// test_union(5); // UnionExample::Long(5)
2497/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
2498/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
2499/// test_union(null); // UnionExample::None
2500/// var_dump(give_union()); // int(5)
2501/// ```
2502// END DOCS FROM zval_convert.md
2503#[proc_macro_derive(ZvalConvert, attributes(php))]
2504pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
2505    zval_convert_derive_internal(input.into()).into()
2506}
2507
2508fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
2509    let input = parse_macro_input2!(input as DeriveInput);
2510
2511    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
2512}
2513
2514/// Defines an `extern` function with the Zend fastcall convention based on
2515/// operating system.
2516///
2517/// On Windows, Zend fastcall functions use the vector calling convention, while
2518/// on all other operating systems no fastcall convention is used (just the
2519/// regular C calling convention).
2520///
2521/// This macro wraps a function and applies the correct calling convention.
2522///
2523/// ## Examples
2524///
2525/// ```rust,ignore
2526/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2527/// use ext_php_rs::zend_fastcall;
2528///
2529/// zend_fastcall! {
2530///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
2531///         a + b
2532///     }
2533/// }
2534/// ```
2535///
2536/// On Windows, this function will have the signature `pub extern "vectorcall"
2537/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
2538/// signature `pub extern "C" fn(i32, i32) -> i32`.
2539///
2540/// ## Support
2541///
2542/// The `vectorcall` ABI is currently only supported on Windows with nightly
2543/// Rust and the `abi_vectorcall` feature enabled.
2544#[proc_macro]
2545pub fn zend_fastcall(input: TokenStream) -> TokenStream {
2546    zend_fastcall_internal(input.into()).into()
2547}
2548
2549fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
2550    let input = parse_macro_input2!(input as ItemFn);
2551
2552    fastcall::parser(input)
2553}
2554
2555/// Wraps a function to be used in the [`Module::function`] method.
2556#[proc_macro]
2557pub fn wrap_function(input: TokenStream) -> TokenStream {
2558    wrap_function_internal(input.into()).into()
2559}
2560
2561fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
2562    let input = parse_macro_input2!(input as syn::Path);
2563
2564    match function::wrap(&input) {
2565        Ok(parsed) => parsed,
2566        Err(e) => e.to_compile_error(),
2567    }
2568}
2569
2570/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
2571#[proc_macro]
2572pub fn wrap_constant(input: TokenStream) -> TokenStream {
2573    wrap_constant_internal(input.into()).into()
2574}
2575
2576fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
2577    let input = parse_macro_input2!(input as syn::Path);
2578
2579    match constant::wrap(&input) {
2580        Ok(parsed) => parsed,
2581        Err(e) => e.to_compile_error(),
2582    }
2583}
2584
2585const PHP_ATTR_HINT: &str = "Use `#[php(<args>)]` on the item instead.";
2586const NO_ARGS_HINT: &str = "It takes no arguments.";
2587
2588fn reject_args(args: &TokenStream2, macro_name: &str, hint: &str) -> Result<(), syn::Error> {
2589    if args.is_empty() {
2590        return Ok(());
2591    }
2592    Err(err!(args => "`#[{macro_name}(<args>)]` args are not supported. {hint}"))
2593}
2594
2595macro_rules! parse_macro_input2 {
2596    ($tokenstream:ident as $ty:ty) => {
2597        match syn::parse2::<$ty>($tokenstream) {
2598            Ok(data) => data,
2599            Err(err) => {
2600                return proc_macro2::TokenStream::from(err.to_compile_error());
2601            }
2602        }
2603    };
2604    ($tokenstream:ident) => {
2605        $crate::parse_macro_input!($tokenstream as _)
2606    };
2607}
2608
2609pub(crate) use parse_macro_input2;
2610
2611macro_rules! err {
2612    ($span:expr => $($msg:tt)*) => {
2613        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
2614    };
2615    ($($msg:tt)*) => {
2616        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
2617    };
2618}
2619
2620/// Bails out of a function with a syn error.
2621macro_rules! bail {
2622    ($span:expr => $($msg:tt)*) => {
2623        return Err($crate::err!($span => $($msg)*))
2624    };
2625    ($($msg:tt)*) => {
2626        return Err($crate::err!($($msg)*))
2627    };
2628}
2629
2630pub(crate) use bail;
2631pub(crate) use err;
2632
2633pub(crate) mod prelude {
2634    pub(crate) trait OptionTokens {
2635        fn option_tokens(&self) -> proc_macro2::TokenStream;
2636    }
2637
2638    impl<T: quote::ToTokens> OptionTokens for Option<T> {
2639        fn option_tokens(&self) -> proc_macro2::TokenStream {
2640            if let Some(token) = self {
2641                quote::quote! { ::std::option::Option::Some(#token) }
2642            } else {
2643                quote::quote! { ::std::option::Option::None }
2644            }
2645        }
2646    }
2647
2648    pub(crate) use crate::{bail, err};
2649    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
2650}
2651
2652#[cfg(test)]
2653mod tests {
2654    use super::*;
2655    use std::path::PathBuf;
2656
2657    type AttributeFn =
2658        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2659    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2660
2661    #[rustversion::attr(nightly, test)]
2662    #[allow(dead_code)]
2663    pub fn test_macrotest_expand() {
2664        macrotest::expand("tests/expand/*.rs");
2665    }
2666
2667    #[test]
2668    pub fn test_expand() {
2669        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
2670            let entry = entry.expect("Failed to read expand test file");
2671            runtime_expand_attr(&entry);
2672            runtime_expand_func(&entry);
2673            runtime_expand_derive(&entry);
2674        }
2675    }
2676
2677    fn runtime_expand_attr(path: &PathBuf) {
2678        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2679        runtime_macros::emulate_attributelike_macro_expansion(
2680            file,
2681            &[
2682                ("php_class", php_class_internal as AttributeFn),
2683                ("php_const", php_const_internal as AttributeFn),
2684                ("php_enum", php_enum_internal as AttributeFn),
2685                ("php_interface", php_interface_internal as AttributeFn),
2686                ("php_extern", php_extern_internal as AttributeFn),
2687                ("php_function", php_function_internal as AttributeFn),
2688                ("php_impl", php_impl_internal as AttributeFn),
2689                (
2690                    "php_impl_interface",
2691                    php_impl_interface_internal as AttributeFn,
2692                ),
2693                ("php_module", php_module_internal as AttributeFn),
2694            ],
2695        )
2696        .expect("Failed to expand attribute macros in test file");
2697    }
2698
2699    fn runtime_expand_func(path: &PathBuf) {
2700        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2701        runtime_macros::emulate_functionlike_macro_expansion(
2702            file,
2703            &[
2704                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
2705                ("wrap_function", wrap_function_internal as FunctionLikeFn),
2706                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
2707            ],
2708        )
2709        .expect("Failed to expand function-like macros in test file");
2710    }
2711
2712    fn runtime_expand_derive(path: &PathBuf) {
2713        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2714        runtime_macros::emulate_derive_macro_expansion(
2715            file,
2716            &[("ZvalConvert", zval_convert_derive_internal)],
2717        )
2718        .expect("Failed to expand derive macros in test file");
2719    }
2720}