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