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