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. If the caller passes a value that the parameter type does not
1429/// accept, the call throws a `TypeError`.
1430///
1431/// A function accepts the `name`, `change_case`, `defaults` and `optional`
1432/// options. PHP functions have no visibility, so `#[php(vis = "...")]` is a
1433/// compile error on a function.
1434///
1435/// ## Optional parameters
1436///
1437/// An `Option<T>` parameter accepts `null` and can be omitted. The macro reads
1438/// this from the type through `FromZvalMut::NULLABLE`, so a type alias such as
1439/// `type MaybeAge = Option<i64>` or the qualified path
1440/// `std::option::Option<i64>` behaves like `Option<i64>`. The trailing run of
1441/// parameters that are nullable or have a default value is optional.
1442///
1443/// ```rust,no_run,ignore
1444/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1445/// # extern crate ext_php_rs;
1446/// use ext_php_rs::prelude::*;
1447///
1448/// #[php_function]
1449/// pub fn greet(name: String, age: Option<i32>) -> String {
1450///     let mut greeting = format!("Hello, {}!", name);
1451///
1452///     if let Some(age) = age {
1453///         greeting += &format!(" You are {} years old.", age);
1454///     }
1455///
1456///     greeting
1457/// }
1458///
1459/// #[php_module]
1460/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1461///     module.function(wrap_function!(greet))
1462/// }
1463/// # fn main() {}
1464/// ```
1465///
1466/// Default parameter values can also be set for optional parameters. This is
1467/// done through the `#[php(defaults)]` attribute option. When an optional
1468/// parameter has a default, it does not need to be a variant of `Option`. The
1469/// default expression is converted into the parameter type with `Into`, and the
1470/// type must implement `StubLiteral`, which every scalar, string, `Option`,
1471/// `Vec` and `HashMap` does. The module renders the value once at load time
1472/// into the stub file and into `ReflectionParameter::getDefaultValue()`. Each
1473/// key in `defaults` must name a parameter of the function. If a key names no
1474/// parameter, the build fails:
1475///
1476/// ```rust,no_run,ignore
1477/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1478/// # extern crate ext_php_rs;
1479/// use ext_php_rs::prelude::*;
1480///
1481/// #[php_function]
1482/// #[php(defaults(offset = 0))]
1483/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
1484///     let haystack: String = haystack.chars().skip(offset as usize).collect();
1485///     haystack.find(needle)
1486/// }
1487///
1488/// #[php_module]
1489/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1490///     module.function(wrap_function!(rusty_strpos))
1491/// }
1492/// # fn main() {}
1493/// ```
1494///
1495/// Note that if there is a non-optional argument after an argument that is a
1496/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
1497/// argument rather than an optional argument.
1498///
1499/// ```rust,no_run,ignore
1500/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1501/// # extern crate ext_php_rs;
1502/// use ext_php_rs::prelude::*;
1503///
1504/// /// `age` will be deemed required and nullable rather than optional.
1505/// #[php_function]
1506/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
1507///     let mut greeting = format!("Hello, {}!", name);
1508///
1509///     if let Some(age) = age {
1510///         greeting += &format!(" You are {} years old.", age);
1511///     }
1512///
1513///     greeting += &format!(" {}.", description);
1514///     greeting
1515/// }
1516///
1517/// #[php_module]
1518/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1519///     module.function(wrap_function!(greet))
1520/// }
1521/// # fn main() {}
1522/// ```
1523///
1524/// You can also specify the optional arguments if you want to have nullable
1525/// arguments before optional arguments. This is done through the `optional`
1526/// attribute option, which names the first optional parameter. PHP callers can
1527/// omit that parameter and every parameter after it. Each of these parameters
1528/// must be an `Option<T>`, have a default, or be the variadic `&[T]` tail.
1529/// Otherwise the build fails with an error on the type of that parameter. If
1530/// `optional` names no parameter, the build also fails:
1531///
1532/// ```rust,no_run,ignore
1533/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1534/// # extern crate ext_php_rs;
1535/// use ext_php_rs::prelude::*;
1536///
1537/// /// `age` will be deemed required and nullable rather than optional,
1538/// /// while description will be optional.
1539/// #[php_function]
1540/// #[php(optional = "description")]
1541/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
1542///     let mut greeting = format!("Hello, {}!", name);
1543///
1544///     if let Some(age) = age {
1545///         greeting += &format!(" You are {} years old.", age);
1546///     }
1547///
1548///     if let Some(description) = description {
1549///         greeting += &format!(" {}.", description);
1550///     }
1551///
1552///     greeting
1553/// }
1554///
1555/// #[php_module]
1556/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1557///     module.function(wrap_function!(greet))
1558/// }
1559/// # fn main() {}
1560/// ```
1561///
1562/// ## Variadic Functions
1563///
1564/// A slice parameter `&[T]` in last position is variadic, the equivalent of the
1565/// PHP `...$args` syntax. `T` is any argument type: `&[&Zval]` receives the raw
1566/// values, `&[i64]` receives converted integers. If one of the values cannot be
1567/// converted into `T`, the call throws a `TypeError`. A variadic parameter that
1568/// is not the last parameter is a compile error. The slice must be spelled in
1569/// the signature; a type alias that hides the slice is not detected.
1570///
1571/// ```rust,no_run,ignore
1572/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1573/// # extern crate ext_php_rs;
1574/// use ext_php_rs::{prelude::*, types::Zval};
1575///
1576/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
1577/// #[php_function]
1578/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
1579///     // numbers is a slice of 4 Zvals all of type long
1580///     number
1581/// }
1582///
1583/// #[php_module]
1584/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1585///     module.function(wrap_function!(add))
1586/// }
1587/// # fn main() {}
1588/// ```
1589///
1590/// ## Performance
1591///
1592/// The `#[php_function]` macro generates a zero-allocation fast path that reads
1593/// arguments directly from the PHP call frame, matching how native C extensions
1594/// parse parameters. This applies automatically — no configuration needed.
1595///
1596/// The same fast path is used for class methods defined with `#[php_impl]`,
1597/// including instance methods (`&self`, `&mut self`) and static methods.
1598///
1599/// ```rust,no_run,ignore
1600/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1601/// # extern crate ext_php_rs;
1602/// # use ext_php_rs::prelude::*;
1603/// // Fast path: standalone functions
1604/// #[php_function]
1605/// pub fn fast(name: String, age: Option<i32>) -> String { name }
1606///
1607/// // Fast path: parameters with defaults
1608/// #[php_function]
1609/// #[php(defaults(offset = 0))]
1610/// pub fn also_fast(haystack: &str, offset: i64) -> i64 { offset }
1611/// # fn main() {}
1612/// ```
1613///
1614/// ```rust,ignore
1615/// #[php_impl]
1616/// impl MyClass {
1617///     // Fast path: static methods
1618///     pub fn create(n: i32) -> Self { /* ... */ }
1619///
1620///     // Fast path: instance methods
1621///     pub fn get_value(&self, offset: i32) -> i32 { /* ... */ }
1622/// }
1623/// ```
1624///
1625/// The only case that falls back to the runtime argument parser is when using
1626/// variadic arguments (`&[T]`, the Rust equivalent of PHP's `...$args`):
1627///
1628/// ```rust,no_run,ignore
1629/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1630/// # extern crate ext_php_rs;
1631/// # use ext_php_rs::{prelude::*, types::Zval};
1632/// // Slower path: variadic arguments require runtime parsing
1633/// #[php_function]
1634/// pub fn add(first: u32, rest: &[&Zval]) -> u32 { first }
1635/// # fn main() {}
1636/// ```
1637///
1638/// ## Returning `Result<T, E>`
1639///
1640/// You can also return a `Result` from the function. The error variant will be
1641/// translated into an exception and thrown. See the section on
1642/// [exceptions](../exceptions.md) for more details.
1643// END DOCS FROM function.md
1644#[proc_macro_attribute]
1645pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
1646    php_function_internal(args.into(), input.into()).into()
1647}
1648
1649#[allow(clippy::needless_pass_by_value)]
1650fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1651    let input = parse_macro_input2!(input as ItemFn);
1652    if let Err(e) = reject_args(&args, "php_function", PHP_ATTR_HINT) {
1653        return e.to_compile_error();
1654    }
1655
1656    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
1657}
1658
1659// BEGIN DOCS FROM constant.md
1660/// # `#[php_const]` Attribute
1661///
1662/// Exports a Rust constant as a global PHP constant. The constant can be any
1663/// type that implements `IntoConst`.
1664///
1665/// The `wrap_constant!()` macro can be used to simplify the registration of
1666/// constants. It sets the name and doc comments for the constant.
1667///
1668/// You can rename the const with options:
1669///
1670/// - `name` - Allows you to rename the property, e.g. `#[php(name =
1671///   "new_name")]`
1672/// - `change_case` - Allows you to rename the property using rename rules, e.g.
1673///   `#[php(change_case = "PascalCase")]`
1674///
1675/// A global constant has no visibility. `#[php(vis = "...")]` on a
1676/// `#[php_const]` is a compile error. Use `vis` on constants inside a
1677/// `#[php_impl]` block.
1678///
1679/// ## Examples
1680///
1681/// ```rust,no_run,ignore
1682/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1683/// # extern crate ext_php_rs;
1684/// use ext_php_rs::prelude::*;
1685///
1686/// #[php_const]
1687/// const TEST_CONSTANT: i32 = 100;
1688///
1689/// #[php_const]
1690/// #[php(name = "I_AM_RENAMED")]
1691/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
1692///
1693/// #[php_const]
1694/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
1695///
1696/// #[php_module]
1697/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1698///     module
1699///         .constant(wrap_constant!(TEST_CONSTANT))
1700///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
1701///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
1702/// }
1703/// # fn main() {}
1704/// ```
1705///
1706/// ## PHP usage
1707///
1708/// ```php
1709/// <?php
1710///
1711/// var_dump(TEST_CONSTANT); // int(100)
1712/// var_dump(I_AM_RENAMED); // int(42)
1713/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
1714/// ```
1715// END DOCS FROM constant.md
1716#[proc_macro_attribute]
1717pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
1718    php_const_internal(args.into(), input.into()).into()
1719}
1720
1721#[allow(clippy::needless_pass_by_value)]
1722fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1723    let input = parse_macro_input2!(input as ItemConst);
1724    if let Err(e) = reject_args(&args, "php_const", PHP_ATTR_HINT) {
1725        return e.to_compile_error();
1726    }
1727
1728    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
1729}
1730
1731// BEGIN DOCS FROM module.md
1732/// # `#[php_module]` Attribute
1733///
1734/// The module macro is used to annotate the `get_module` function, which is
1735/// used by the PHP interpreter to retrieve information about your extension,
1736/// including the name, version, functions and extra initialization functions.
1737/// Regardless if you use this macro, your extension requires a `extern "C" fn
1738/// get_module()` so that PHP can get this information.
1739///
1740/// The function is renamed to `get_module` if you have used another name. The
1741/// macro also exports a crate-prefixed `<crate_name>_get_module` alias, used
1742/// when [statically linking the extension into
1743/// php-src](../advanced/static_linking.md). Building with
1744/// `EXT_PHP_RS_STATIC_EXT=1` removes the unmangled `get_module` export (the
1745/// function itself stays callable from Rust) so a statically linked
1746/// extension cannot collide with another extension exporting `get_module`.
1747/// Dynamic builds (`extension=` / `dl()`) must not set the variable.
1748///
1749/// The function is passed an instance of `ModuleBuilder` which allows you to
1750/// register the following (if required):
1751///
1752/// - Functions, classes, and constants
1753/// - Extension and request startup and shutdown functions.
1754///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
1755/// - PHP extension information function
1756///   - Used by the `phpinfo()` function to get information about your
1757///     extension.
1758///
1759/// Classes and constants are not registered with PHP in the `get_module`
1760/// function. These are registered inside the extension startup function.
1761///
1762/// ## Startup failures
1763///
1764/// If a constant, an interface, a class or an enum cannot be registered, the
1765/// generated startup function logs the cause as an `E_CORE_WARNING` and returns
1766/// `FAILURE`. PHP then reports `Unable to start <extension> module`. At engine
1767/// startup PHP exits. From `dl()` the request fails. A panic during
1768/// registration gets the same treatment. The process does not abort in either
1769/// case.
1770///
1771/// `get_module` cannot report a failure, because PHP reads the returned entry
1772/// without a check. If the `ModuleBuilder` cannot become a module entry, for
1773/// example because a function name, an argument name or the module name
1774/// contains a NUL byte, the macro returns a placeholder entry with the name of
1775/// the crate. The startup function of that entry logs the build error and fails
1776/// as described above.
1777///
1778/// The `startup` function that you name in `#[php(startup = ...)]` is your own
1779/// `extern "C"` function. A panic inside it is not caught.
1780///
1781/// You can also give a startup function to `ModuleBuilder::startup_function`.
1782/// The generated startup function calls it after the `#[php(startup = ...)]`
1783/// function. The two functions run before the macro registers the constants,
1784/// interfaces, classes and enums.
1785///
1786/// ## Usage
1787///
1788/// ```rust,no_run,ignore
1789/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1790/// # extern crate ext_php_rs;
1791/// use ext_php_rs::{
1792///     prelude::*,
1793///     zend::ModuleEntry,
1794///     info_table_start,
1795///     info_table_row,
1796///     info_table_end
1797/// };
1798///
1799/// #[php_const]
1800/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
1801///
1802/// #[php_class]
1803/// pub struct Test {
1804///     a: i32,
1805///     b: i32
1806/// }
1807/// #[php_function]
1808/// pub fn hello_world() -> &'static str {
1809///     "Hello, world!"
1810/// }
1811///
1812/// /// Used by the `phpinfo()` function and when you run `php -i`.
1813/// /// This will probably be simplified with another macro eventually!
1814/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
1815///     info_table_start!();
1816///     info_table_row!("my extension", "enabled");
1817///     info_table_end!();
1818/// }
1819///
1820/// #[php_module]
1821/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1822///     module
1823///         .constant(wrap_constant!(MY_CUSTOM_CONST))
1824///         .class::<Test>()
1825///         .function(wrap_function!(hello_world))
1826///         .info_function(php_module_info)
1827/// }
1828/// # fn main() {}
1829/// ```
1830// END DOCS FROM module.md
1831#[proc_macro_attribute]
1832pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
1833    php_module_internal(args.into(), input.into()).into()
1834}
1835
1836#[allow(clippy::needless_pass_by_value)]
1837fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1838    let input = parse_macro_input2!(input as ItemFn);
1839    if let Err(e) = reject_args(&args, "php_module", PHP_ATTR_HINT) {
1840        return e.to_compile_error();
1841    }
1842
1843    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
1844}
1845
1846// BEGIN DOCS FROM impl.md
1847/// # `#[php_impl]` Attribute
1848///
1849/// You can export an entire `impl` block to PHP. This exports all methods as
1850/// well as constants to PHP on the class that it is implemented on. This
1851/// requires the `#[php_class]` macro to already be used on the underlying
1852/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
1853/// block can be exported per class.
1854///
1855/// If you do not want a function exported to PHP, you should place it in a
1856/// separate `impl` block.
1857///
1858/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
1859/// &raquo;](./async_impl.md) for more info.
1860///
1861/// ## Options
1862///
1863/// By default all constants are renamed to `UPPER_CASE` and all methods are
1864/// renamed to camelCase. This can be changed by passing the
1865/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
1866/// the `impl` block. The options are:
1867///
1868/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
1869///   case.
1870/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
1871///   snake case.
1872///
1873/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
1874/// for a list of all available cases.
1875///
1876/// ## Methods
1877///
1878/// Methods basically follow the same rules as functions, so read about the
1879/// [`php_function`] macro first. The primary difference between functions and
1880/// methods is they are bounded by their class object.
1881///
1882/// Both instance and static methods benefit from the same zero-allocation fast
1883/// path as standalone functions. See the
1884/// [Performance](./function.md#performance) section for details.
1885///
1886/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
1887/// a consuming `self` parameter. Static methods can omit this `self` parameter.
1888///
1889/// To access the underlying Zend object, you can take a reference to a
1890/// `ZendClassObject<T>` in place of the self parameter, where the parameter
1891/// must be named `self_`. This can also be used to return a reference to
1892/// `$this`.
1893///
1894/// The rest of the options are passed as separate attributes:
1895///
1896/// - `#[php(defaults(i = 5, b = "\"hello\""))]` - Sets the default value for
1897///   parameter(s). Each key must name a parameter.
1898/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
1899///   also sets the remaining parameters as optional, so each of them must be an
1900///   `Option<T>` or have a default. Otherwise the build fails.
1901/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
1902///   "private")]` - Sets the visibility of the method.
1903/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
1904///   identifier, without renaming the Rust method name.
1905/// - `#[php(final)]` - Makes the method final (cannot be overridden in
1906///   subclasses).
1907/// - `#[php(abstract)]` - Makes the method abstract (must be implemented by
1908///   subclasses). Can only be used in abstract classes.
1909///
1910/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
1911/// the equivalent function attribute parameters. A `self_` parameter is the PHP
1912/// object, not a PHP parameter, so `defaults` and `optional` cannot name it. A
1913/// `getter` or `setter` method accepts neither option.
1914///
1915/// ### Static Methods
1916///
1917/// Methods that do not take a `&self` or `&mut self` parameter are
1918/// automatically exported as static methods. These can be called on the class
1919/// itself without creating an instance.
1920///
1921/// ```rust,ignore
1922/// #[php_impl]
1923/// impl MyClass {
1924///     // Static method - no self parameter
1925///     pub fn create_default() -> Self {
1926///         Self { /* ... */ }
1927///     }
1928///
1929///     // Instance method - takes &self
1930///     pub fn get_value(&self) -> i32 {
1931///         self.value
1932///     }
1933/// }
1934/// ```
1935///
1936/// From PHP:
1937///
1938/// ```php
1939/// $obj = MyClass::createDefault(); // Static call
1940/// $val = $obj->getValue();         // Instance call
1941/// ```
1942///
1943/// ### Final Methods
1944///
1945/// Methods marked with `#[php(final)]` cannot be overridden in subclasses. This
1946/// is useful when you want to prevent modification of critical functionality.
1947///
1948/// ```rust,ignore
1949/// use ext_php_rs::prelude::*;
1950///
1951/// #[php_class]
1952/// pub struct SecureClass;
1953///
1954/// #[php_impl]
1955/// impl SecureClass {
1956///     #[php(final)]
1957///     pub fn secure_method(&self) -> &str {
1958///         "This cannot be overridden"
1959///     }
1960///
1961///     // Final static methods are also supported
1962///     #[php(final)]
1963///     pub fn secure_static() -> i32 {
1964///         42
1965///     }
1966/// }
1967/// ```
1968///
1969/// ### Abstract Methods
1970///
1971/// Methods marked with `#[php(abstract)]` must be implemented by subclasses.
1972/// Abstract methods can only be defined in abstract classes (classes with
1973/// `ClassFlags::Abstract`).
1974///
1975/// ```rust,ignore
1976/// use ext_php_rs::prelude::*;
1977/// use ext_php_rs::flags::ClassFlags;
1978///
1979/// #[php_class]
1980/// #[php(flags = ClassFlags::Abstract)]
1981/// pub struct AbstractShape;
1982///
1983/// #[php_impl]
1984/// impl AbstractShape {
1985///     // Protected constructor for subclasses
1986///     #[php(vis = "protected")]
1987///     pub fn __construct() -> Self {
1988///         Self
1989///     }
1990///
1991///     // Abstract method - subclasses must implement this.
1992///     // The body is never called; use unimplemented!() as a placeholder.
1993///     #[php(abstract)]
1994///     pub fn area(&self) -> f64 {
1995///         unimplemented!()
1996///     }
1997///
1998///     // Concrete method in abstract class
1999///     pub fn describe(&self) -> String {
2000///         format!("A shape with area {}", self.area())
2001///     }
2002/// }
2003/// ```
2004///
2005/// **Note:** Abstract method bodies are never called - they exist only because
2006/// Rust syntax requires a body for methods in `impl` blocks. Use
2007/// `unimplemented!()` as a clear placeholder.
2008///
2009/// **Note:** If you try to use `#[php(abstract)]` on a method in a non-abstract
2010/// class, you will get a compile-time error.
2011///
2012/// **Note:** PHP does not support abstract static methods. If you need static
2013/// behavior that can be customized by subclasses, use a regular instance method
2014/// or the [late static binding](https://www.php.net/manual/en/language.oop5.late-static-bindings.php)
2015/// pattern in PHP.
2016///
2017/// ### Constructors
2018///
2019/// By default, if a class does not have a constructor, it is not constructable
2020/// from PHP. It can only be returned from a Rust function to PHP.
2021///
2022/// Constructors are Rust methods which can take any amount of parameters and
2023/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
2024/// When the error variant of `Result` is encountered, it is thrown as an
2025/// exception and the class is not constructed.
2026///
2027/// Constructors are designated by either naming the method `__construct` or by
2028/// annotating a method with the `#[php(constructor)]` attribute. Note that when
2029/// using the attribute, the function is not exported to PHP like a regular
2030/// method.
2031///
2032/// Constructors cannot use the visibility or rename attributes listed above.
2033///
2034/// ## Constants
2035///
2036/// Constants are defined as regular Rust `impl` constants. Any type that
2037/// implements `IntoZval` can be used as a constant. A constant accepts the
2038/// `name`, `change_case` and `vis` options. The default visibility is `public`.
2039///
2040/// ```rust,no_run,ignore
2041/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2042/// # extern crate ext_php_rs;
2043/// # use ext_php_rs::prelude::*;
2044/// #[php_class]
2045/// pub struct Limits;
2046///
2047/// #[php_impl]
2048/// impl Limits {
2049///     const MAX_USERS: i64 = 100;
2050///
2051///     #[php(vis = "protected")]
2052///     const MAX_RETRIES: i64 = 3;
2053///
2054///     #[php(vis = "private", name = "SEED")]
2055///     const RANDOM_SEED: i64 = 42;
2056/// }
2057/// # fn main() {}
2058/// ```
2059///
2060/// PHP sees `public const MAX_USERS`, `protected const MAX_RETRIES` and
2061/// `private const SEED`. Reflection reports the same visibility, and the
2062/// generated stubs print it.
2063///
2064/// ## Property getters and setters
2065///
2066/// A class can expose a property whose value comes from Rust methods. Mark the
2067/// method that reads the value with `#[php(getter)]` and the method that writes
2068/// it with `#[php(setter)]`. The property name is the method name without its
2069/// `get_` or `set_` prefix, converted to camelCase. This is the same rule as
2070/// for `#[php(prop)]` fields: `get_first_name` gives the property `firstName`.
2071/// Properties are declared in the order in which their first
2072/// getter or setter appears in the `impl` block, after the `#[php(prop)]` field
2073/// properties of the struct.
2074///
2075/// If you want a different property name, pass a `name` or `change_case` option
2076/// to the `#[php]` attribute: `#[php(getter, name = "first_name")]` or
2077/// `#[php(getter, change_case = "snake_case")]`.
2078///
2079/// A property does not need both methods. A property with only a getter is
2080/// read-only. A property with only a setter is write-only.
2081///
2082/// One method cannot be both a getter and a setter. A property cannot have two
2083/// getters or two setters. The getter and the setter of one property must have
2084/// the same visibility. Each of these cases is a compile error.
2085///
2086/// A method property cannot have the same name as a `#[php(prop)]` field of the
2087/// struct. The two macros are expanded separately, so this is not a compile
2088/// error. The extension fails to load with this error:
2089///
2090/// ```text
2091/// property `Book::$title` is declared twice
2092/// ```
2093///
2094/// The PHP type of the property comes from the return type of the getter. If
2095/// the property has no getter, the type comes from the argument of the setter.
2096/// The documentation of the property comes from the getter, or from the setter
2097/// when the getter has none. Like field properties, the type must implement
2098/// `IntoZval` and `FromZval`.
2099///
2100/// ## Example
2101///
2102/// Continuing on from our `Human` example in the structs section, we will
2103/// define a constructor, as well as getters for the properties. We will also
2104/// define a constant for the maximum age of a `Human`.
2105///
2106/// ```rust,no_run,ignore
2107/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2108/// # extern crate ext_php_rs;
2109/// use ext_php_rs::{prelude::*, types::ZendClassObject};
2110///
2111/// #[php_class]
2112/// #[derive(Debug, Default)]
2113/// pub struct Human {
2114///     name: String,
2115///     age: i32,
2116///     #[php(prop)]
2117///     address: String,
2118/// }
2119///
2120/// #[php_impl]
2121/// impl Human {
2122///     const MAX_AGE: i32 = 100;
2123///
2124///     // No `#[constructor]` attribute required here - the name is `__construct`.
2125///     pub fn __construct(name: String, age: i32) -> Self {
2126///         Self {
2127///             name,
2128///             age,
2129///             address: String::new()
2130///         }
2131///     }
2132///
2133///     #[php(getter)]
2134///     pub fn get_name(&self) -> String {
2135///         self.name.to_string()
2136///     }
2137///
2138///     #[php(setter)]
2139///     pub fn set_name(&mut self, name: String) {
2140///         self.name = name;
2141///     }
2142///
2143///     #[php(getter)]
2144///     pub fn get_age(&self) -> i32 {
2145///         self.age
2146///     }
2147///
2148///     pub fn introduce(&self) {
2149///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
2150///     }
2151///
2152///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
2153///         dbg!(self_)
2154///     }
2155///
2156///     pub fn get_max_age() -> i32 {
2157///         Self::MAX_AGE
2158///     }
2159/// }
2160/// #[php_module]
2161/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2162///     module.class::<Human>()
2163/// }
2164/// # fn main() {}
2165/// ```
2166///
2167/// Using our newly created class in PHP:
2168///
2169/// ```php
2170/// <?php
2171///
2172/// $me = new Human('David', 20);
2173///
2174/// $me->introduce(); // My name is David and I am 20 years old.
2175/// var_dump(Human::get_max_age()); // int(100)
2176/// var_dump(Human::MAX_AGE); // int(100)
2177/// ```
2178///
2179/// [`php_async_impl`]: ./async_impl.md
2180// END DOCS FROM impl.md
2181#[proc_macro_attribute]
2182pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
2183    php_impl_internal(args.into(), input.into()).into()
2184}
2185
2186#[allow(clippy::needless_pass_by_value)]
2187fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2188    let input = parse_macro_input2!(input as ItemImpl);
2189    if let Err(e) = reject_args(&args, "php_impl", PHP_ATTR_HINT) {
2190        return e.to_compile_error();
2191    }
2192
2193    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2194}
2195
2196/// # `#[php_impl_interface]` Attribute
2197///
2198/// Marks a trait implementation as implementing a PHP interface. This allows
2199/// Rust structs marked with `#[php_class]` to implement Rust traits marked
2200/// with `#[php_interface]`, and have PHP recognize the relationship.
2201///
2202/// **Key feature**: The macro automatically registers the trait methods as PHP
2203/// methods on the class. You don't need to duplicate them in a separate
2204/// `#[php_impl]` block.
2205///
2206/// ## Usage
2207///
2208/// ```rust,no_run,ignore
2209/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2210/// # extern crate ext_php_rs;
2211/// use ext_php_rs::prelude::*;
2212///
2213/// #[php_interface]
2214/// trait MyInterface {
2215///     fn my_method(&self) -> String;
2216/// }
2217///
2218/// #[php_class]
2219/// struct MyClass;
2220///
2221/// // The trait method my_method() is automatically registered as a PHP method
2222/// #[php_impl_interface]
2223/// impl MyInterface for MyClass {
2224///     fn my_method(&self) -> String {
2225///         "Hello from MyClass!".to_string()
2226///     }
2227/// }
2228///
2229/// #[php_module]
2230/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2231///     module
2232///         .interface::<PhpInterfaceMyInterface>()
2233///         .class::<MyClass>()
2234/// }
2235/// # fn main() {}
2236/// ```
2237///
2238/// After registration, PHP's `is_a($obj, 'MyInterface')` will return `true`
2239/// for instances of `MyClass`, and `$obj->myMethod()` will be callable.
2240///
2241/// ## Method names
2242///
2243/// Each method is registered under the name that `#[php_interface]` gave it.
2244/// The trait's `change_method_case` and the `name` or `change_case` of a trait
2245/// method apply to the class too. The macro takes no arguments:
2246/// `#[php_impl_interface(change_method_case = "...")]` is a compile error.
2247///
2248/// ## Requirements
2249///
2250/// - The trait must be marked with `#[php_interface]`
2251/// - The struct must be marked with `#[php_class]`
2252/// - The interface must be registered before the class in the module builder
2253#[proc_macro_attribute]
2254pub fn php_impl_interface(args: TokenStream, input: TokenStream) -> TokenStream {
2255    php_impl_interface_internal(args.into(), input.into()).into()
2256}
2257
2258#[allow(clippy::needless_pass_by_value)]
2259fn php_impl_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2260    let input = parse_macro_input2!(input as ItemImpl);
2261    let attr_args = match darling::ast::NestedMeta::parse_meta_list(args) {
2262        Ok(v) => v,
2263        Err(e) => return e.to_compile_error(),
2264    };
2265    let args = match impl_interface::PhpImplInterfaceArgs::from_list(&attr_args) {
2266        Ok(v) => v,
2267        Err(e) => return e.write_errors(),
2268    };
2269
2270    impl_interface::parser(&args, &input).unwrap_or_else(|e| e.to_compile_error())
2271}
2272
2273// BEGIN DOCS FROM extern.md
2274/// # `#[php_extern]` Attribute
2275///
2276/// Attribute used to annotate `extern` blocks which are deemed as PHP
2277/// functions.
2278///
2279/// This allows you to 'import' PHP functions into Rust so that they can be
2280/// called like regular Rust functions. Parameters can be any type that
2281/// implements [`IntoZval`], and the return type can be anything that implements
2282/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
2283/// case).
2284///
2285/// Unlike most other attributes, this does not need to be placed inside a
2286/// `#[php_module]` block.
2287///
2288/// # Panics
2289///
2290/// The function can panic when called under a few circumstances:
2291///
2292/// * The function could not be found or was not callable.
2293/// * One of the parameters could not be converted into a [`Zval`].
2294/// * The actual function call failed internally.
2295/// * The output [`Zval`] could not be parsed into the output type.
2296///
2297/// Inside a function that PHP calls, these panics become a PHP `Error`. The
2298/// process does not abort. See [Exceptions](../exceptions.md#panics).
2299///
2300/// The last point can be important when interacting with functions that return
2301/// unions, such as [`strpos`] which can return an integer or a boolean. In this
2302/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
2303/// invalid, and vice versa.
2304///
2305/// # Example
2306///
2307/// This `extern` block imports the [`strpos`] function from PHP. Notice that
2308/// the string parameters can take either [`String`] or [`&str`], the optional
2309/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
2310/// as the return type is an integer-boolean union.
2311///
2312/// ```rust,no_run,ignore
2313/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2314/// # extern crate ext_php_rs;
2315/// use ext_php_rs::{
2316///     prelude::*,
2317///     types::Zval,
2318/// };
2319///
2320/// #[php_extern]
2321/// extern "C" {
2322///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
2323/// }
2324///
2325/// #[php_function]
2326/// pub fn my_strpos() {
2327///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
2328/// }
2329///
2330/// #[php_module]
2331/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
2332///     module.function(wrap_function!(my_strpos))
2333/// }
2334/// # fn main() {}
2335/// ```
2336///
2337/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
2338/// [`IntoZval`]: crate::convert::IntoZval
2339/// [`Zval`]: crate::types::Zval
2340// END DOCS FROM extern.md
2341#[proc_macro_attribute]
2342pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
2343    php_extern_internal(args.into(), input.into()).into()
2344}
2345
2346#[allow(clippy::needless_pass_by_value)]
2347fn php_extern_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2348    let input = parse_macro_input2!(input as ItemForeignMod);
2349    if let Err(e) = reject_args(&args, "php_extern", NO_ARGS_HINT) {
2350        return e.to_compile_error();
2351    }
2352
2353    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2354}
2355
2356// BEGIN DOCS FROM zval_convert.md
2357/// # `ZvalConvert` Derive Macro
2358///
2359/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
2360/// traits on a struct or enum.
2361///
2362/// ## Structs
2363///
2364/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
2365/// also implemented, mapping fields to properties in both directions. All
2366/// fields on the struct must implement `FromZval` as well. Generics are allowed
2367/// on structs that use the derive macro, however, the implementation will add a
2368/// `FromZval` bound to all generics types.
2369///
2370/// ### Examples
2371///
2372/// ```rust,no_run,ignore
2373/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2374/// # extern crate ext_php_rs;
2375/// use ext_php_rs::prelude::*;
2376///
2377/// #[derive(ZvalConvert)]
2378/// pub struct ExampleClass<'a> {
2379///     a: i32,
2380///     b: String,
2381///     c: &'a str
2382/// }
2383///
2384/// #[php_function]
2385/// pub fn take_object(obj: ExampleClass) {
2386///     dbg!(obj.a, obj.b, obj.c);
2387/// }
2388///
2389/// #[php_function]
2390/// pub fn give_object() -> ExampleClass<'static> {
2391///     ExampleClass {
2392///         a: 5,
2393///         b: "String".to_string(),
2394///         c: "Borrowed",
2395///     }
2396/// }
2397///
2398/// #[php_module]
2399/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2400///     module
2401///         .function(wrap_function!(take_object))
2402///         .function(wrap_function!(give_object))
2403/// }
2404/// # fn main() {}
2405/// ```
2406///
2407/// Calling from PHP:
2408///
2409/// ```php
2410/// <?php
2411///
2412/// $obj = new stdClass;
2413/// $obj->a = 5;
2414/// $obj->b = 'Hello, world!';
2415/// $obj->c = 'another string';
2416///
2417/// take_object($obj);
2418/// var_dump(give_object());
2419/// ```
2420///
2421/// Another example involving generics:
2422///
2423/// ```rust,no_run,ignore
2424/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2425/// # extern crate ext_php_rs;
2426/// use ext_php_rs::prelude::*;
2427///
2428/// // T must implement both `PartialEq<i32>` and `FromZval`.
2429/// #[derive(Debug, ZvalConvert)]
2430/// pub struct CompareVals<T: PartialEq<i32>> {
2431///     a: T,
2432///     b: T
2433/// }
2434///
2435/// #[php_function]
2436/// pub fn take_object(obj: CompareVals<i32>) {
2437///     dbg!(obj);
2438/// }
2439///
2440/// #[php_module]
2441/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2442///     module
2443///         .function(wrap_function!(take_object))
2444/// }
2445/// # fn main() {}
2446/// ```
2447///
2448/// ## Enums
2449///
2450/// When used on an enum, the `FromZval` implementation will treat the enum as a
2451/// tagged union with a mixed datatype. This allows you to accept multiple types
2452/// in a parameter, for example, a string and an integer.
2453///
2454/// The enum variants must not have named fields, and each variant must have
2455/// exactly one field (the type to extract from the zval). Optionally, the enum
2456/// may have one default variant with no data contained, which will be used when
2457/// the rest of the variants could not be extracted from the zval.
2458///
2459/// The ordering of the variants in the enum is important, as the `FromZval`
2460/// implementation will attempt to parse the zval data in order. For example, if
2461/// you put a `String` variant before an integer variant, the integer would be
2462/// converted to a string and passed as the string variant. Because an `f64` or
2463/// `f32` variant also accepts an integer, the integer variants must come before
2464/// it.
2465///
2466/// ### Examples
2467///
2468/// Basic example showing the importance of variant ordering and default field:
2469///
2470/// ```rust,no_run,ignore
2471/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2472/// # extern crate ext_php_rs;
2473/// use ext_php_rs::prelude::*;
2474///
2475/// #[derive(Debug, ZvalConvert)]
2476/// pub enum UnionExample<'a> {
2477///     Long(u64), // Long
2478///     ProperStr(&'a str), // Actual string - not a converted value
2479///     ParsedStr(String), // Potentially parsed string, i.e. a double
2480///     None // Zval did not contain anything that could be parsed above
2481/// }
2482///
2483/// #[php_function]
2484/// pub fn test_union(val: UnionExample) {
2485///     dbg!(val);
2486/// }
2487///
2488/// #[php_function]
2489/// pub fn give_union() -> UnionExample<'static> {
2490///     UnionExample::Long(5)
2491/// }
2492///
2493/// #[php_module]
2494/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2495///     module
2496///         .function(wrap_function!(test_union))
2497///         .function(wrap_function!(give_union))
2498/// }
2499/// # fn main() {}
2500/// ```
2501///
2502/// Use in PHP:
2503///
2504/// ```php
2505/// test_union(5); // UnionExample::Long(5)
2506/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
2507/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
2508/// test_union(null); // UnionExample::None
2509/// var_dump(give_union()); // int(5)
2510/// ```
2511// END DOCS FROM zval_convert.md
2512#[proc_macro_derive(ZvalConvert, attributes(php))]
2513pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
2514    zval_convert_derive_internal(input.into()).into()
2515}
2516
2517fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
2518    let input = parse_macro_input2!(input as DeriveInput);
2519
2520    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
2521}
2522
2523/// Defines an `extern` function with the Zend fastcall convention based on
2524/// operating system.
2525///
2526/// On Windows, Zend fastcall functions use the vector calling convention, while
2527/// on all other operating systems no fastcall convention is used (just the
2528/// regular C calling convention).
2529///
2530/// This macro wraps a function and applies the correct calling convention.
2531///
2532/// ## Examples
2533///
2534/// ```rust,ignore
2535/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2536/// use ext_php_rs::zend_fastcall;
2537///
2538/// zend_fastcall! {
2539///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
2540///         a + b
2541///     }
2542/// }
2543/// ```
2544///
2545/// On Windows, this function will have the signature `pub extern "vectorcall"
2546/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
2547/// signature `pub extern "C" fn(i32, i32) -> i32`.
2548///
2549/// ## Support
2550///
2551/// The `vectorcall` ABI is currently only supported on Windows with nightly
2552/// Rust and the `abi_vectorcall` feature enabled.
2553#[proc_macro]
2554pub fn zend_fastcall(input: TokenStream) -> TokenStream {
2555    zend_fastcall_internal(input.into()).into()
2556}
2557
2558fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
2559    let input = parse_macro_input2!(input as ItemFn);
2560
2561    fastcall::parser(input)
2562}
2563
2564/// Wraps a function to be used in the [`Module::function`] method.
2565#[proc_macro]
2566pub fn wrap_function(input: TokenStream) -> TokenStream {
2567    wrap_function_internal(input.into()).into()
2568}
2569
2570fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
2571    let input = parse_macro_input2!(input as syn::Path);
2572
2573    match function::wrap(&input) {
2574        Ok(parsed) => parsed,
2575        Err(e) => e.to_compile_error(),
2576    }
2577}
2578
2579/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
2580#[proc_macro]
2581pub fn wrap_constant(input: TokenStream) -> TokenStream {
2582    wrap_constant_internal(input.into()).into()
2583}
2584
2585fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
2586    let input = parse_macro_input2!(input as syn::Path);
2587
2588    match constant::wrap(&input) {
2589        Ok(parsed) => parsed,
2590        Err(e) => e.to_compile_error(),
2591    }
2592}
2593
2594const PHP_ATTR_HINT: &str = "Use `#[php(<args>)]` on the item instead.";
2595const NO_ARGS_HINT: &str = "It takes no arguments.";
2596
2597fn reject_args(args: &TokenStream2, macro_name: &str, hint: &str) -> Result<(), syn::Error> {
2598    if args.is_empty() {
2599        return Ok(());
2600    }
2601    Err(err!(args => "`#[{macro_name}(<args>)]` args are not supported. {hint}"))
2602}
2603
2604macro_rules! parse_macro_input2 {
2605    ($tokenstream:ident as $ty:ty) => {
2606        match syn::parse2::<$ty>($tokenstream) {
2607            Ok(data) => data,
2608            Err(err) => {
2609                return proc_macro2::TokenStream::from(err.to_compile_error());
2610            }
2611        }
2612    };
2613    ($tokenstream:ident) => {
2614        $crate::parse_macro_input!($tokenstream as _)
2615    };
2616}
2617
2618pub(crate) use parse_macro_input2;
2619
2620macro_rules! err {
2621    ($span:expr => $($msg:tt)*) => {
2622        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
2623    };
2624    ($($msg:tt)*) => {
2625        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
2626    };
2627}
2628
2629/// Bails out of a function with a syn error.
2630macro_rules! bail {
2631    ($span:expr => $($msg:tt)*) => {
2632        return Err($crate::err!($span => $($msg)*))
2633    };
2634    ($($msg:tt)*) => {
2635        return Err($crate::err!($($msg)*))
2636    };
2637}
2638
2639pub(crate) use bail;
2640pub(crate) use err;
2641
2642pub(crate) mod prelude {
2643    pub(crate) trait OptionTokens {
2644        fn option_tokens(&self) -> proc_macro2::TokenStream;
2645    }
2646
2647    impl<T: quote::ToTokens> OptionTokens for Option<T> {
2648        fn option_tokens(&self) -> proc_macro2::TokenStream {
2649            if let Some(token) = self {
2650                quote::quote! { ::std::option::Option::Some(#token) }
2651            } else {
2652                quote::quote! { ::std::option::Option::None }
2653            }
2654        }
2655    }
2656
2657    pub(crate) use crate::{bail, err};
2658    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
2659}
2660
2661#[cfg(test)]
2662mod tests {
2663    use super::*;
2664    use std::path::PathBuf;
2665
2666    type AttributeFn =
2667        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2668    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2669
2670    #[test]
2671    pub fn test_expand() {
2672        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
2673            let entry = entry.expect("Failed to read expand test file");
2674            runtime_expand_attr(&entry);
2675            runtime_expand_func(&entry);
2676            runtime_expand_derive(&entry);
2677        }
2678    }
2679
2680    fn runtime_expand_attr(path: &PathBuf) {
2681        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2682        runtime_macros::emulate_attributelike_macro_expansion(
2683            file,
2684            &[
2685                ("php_class", php_class_internal as AttributeFn),
2686                ("php_const", php_const_internal as AttributeFn),
2687                ("php_enum", php_enum_internal as AttributeFn),
2688                ("php_interface", php_interface_internal as AttributeFn),
2689                ("php_extern", php_extern_internal as AttributeFn),
2690                ("php_function", php_function_internal as AttributeFn),
2691                ("php_impl", php_impl_internal as AttributeFn),
2692                (
2693                    "php_impl_interface",
2694                    php_impl_interface_internal as AttributeFn,
2695                ),
2696                ("php_module", php_module_internal as AttributeFn),
2697            ],
2698        )
2699        .expect("Failed to expand attribute macros in test file");
2700    }
2701
2702    fn runtime_expand_func(path: &PathBuf) {
2703        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2704        runtime_macros::emulate_functionlike_macro_expansion(
2705            file,
2706            &[
2707                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
2708                ("wrap_function", wrap_function_internal as FunctionLikeFn),
2709                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
2710            ],
2711        )
2712        .expect("Failed to expand function-like macros in test file");
2713    }
2714
2715    fn runtime_expand_derive(path: &PathBuf) {
2716        let file = std::fs::File::open(path).expect("Failed to open expand test file");
2717        runtime_macros::emulate_derive_macro_expansion(
2718            file,
2719            &[("ZvalConvert", zval_convert_derive_internal)],
2720        )
2721        .expect("Failed to expand derive macros in test file");
2722    }
2723}