• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 18244744117

04 Oct 2025 01:08PM UTC coverage: 30.759% (+3.0%) from 27.728%
18244744117

Pull #533

github

web-flow
Merge 0936605ac into 113ef33a0
Pull Request #533: Feat/interface impl

68 of 152 new or added lines in 9 files covered. (44.74%)

3 existing lines in 2 files now uncovered.

1326 of 4311 relevant lines covered (30.76%)

7.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.86
/crates/macros/src/lib.rs
1
//! Macros for the `php-ext` crate.
2
mod class;
3
mod constant;
4
mod enum_;
5
mod extern_;
6
mod fastcall;
7
mod function;
8
mod helpers;
9
mod impl_;
10
mod interface;
11
mod module;
12
mod parsing;
13
mod syn_ext;
14
mod zval;
15

16
use proc_macro::TokenStream;
17
use proc_macro2::TokenStream as TokenStream2;
18
use syn::{
19
    DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, ItemTrait,
20
};
21

22
extern crate proc_macro;
23

24
// BEGIN DOCS FROM classes.md
25
/// # `#[php_class]` Attribute
26
///
27
/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
28
/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
29
/// well as registering the class to be registered with the `#[php_module]`
30
/// macro.
31
///
32
/// ## Options
33
///
34
/// There are additional macros that modify the class. These macros **must** be
35
/// placed underneath the `#[php_class]` attribute.
36
///
37
/// - `name` - Changes the name of the class when exported to PHP. The Rust
38
///   struct name is kept the same. If no name is given, the name of the struct
39
///   is used. Useful for namespacing classes.
40
/// - `change_case` - Changes the case of the class name when exported to PHP.
41
/// - `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - Sets the parent
42
///   class of the class. Can only be used once. `ce_fn` must be a function with
43
///   the signature `fn() -> &'static ClassEntry`.
44
/// - `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the
45
///   given interface on the class. Can be used multiple times. `ce_fn` must be
46
///   a valid function with the signature `fn() -> &'static ClassEntry`.
47
///
48
/// You may also use the `#[php(prop)]` attribute on a struct field to use the
49
/// field as a PHP property. By default, the field will be accessible from PHP
50
/// publicly with the same name as the field. Property types must implement
51
/// `IntoZval` and `FromZval`.
52
///
53
/// You can rename the property with options:
54
///
55
/// - `name` - Allows you to rename the property, e.g. `#[php(name =
56
///   "new_name")]`
57
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
58
///   `#[php(change_case = PascalCase)]`
59
///
60
/// ## Restrictions
61
///
62
/// ### No lifetime parameters
63
///
64
/// Rust lifetimes are used by the Rust compiler to reason about a program's
65
/// memory safety. They are a compile-time only concept;
66
/// there is no way to access Rust lifetimes at runtime from a dynamic language
67
/// like PHP.
68
///
69
/// As soon as Rust data is exposed to PHP,
70
/// there is no guarantee which the Rust compiler can make on how long the data
71
/// will live. PHP is a reference-counted language and those references can be
72
/// held for an arbitrarily long time, which is untraceable by the Rust
73
/// compiler. The only possible way to express this correctly is to require that
74
/// any `#[php_class]` does not borrow data for any lifetime shorter than the
75
/// `'static` lifetime, i.e. the `#[php_class]` cannot have any lifetime
76
/// parameters.
77
///
78
/// When you need to share ownership of data between PHP and Rust,
79
/// instead of using borrowed references with lifetimes, consider using
80
/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
81
///
82
/// ### No generic parameters
83
///
84
/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled
85
/// implementations each time it is used with a different concrete type for `T`.
86
/// These new implementations are generated by the compiler at each usage site.
87
/// This is incompatible with wrapping `Foo` in PHP,
88
/// where there needs to be a single compiled implementation of `Foo` which is
89
/// integrated with the PHP interpreter.
90
///
91
/// ## Example
92
///
93
/// This example creates a PHP class `Human`, adding a PHP property `address`.
94
///
95
/// ```rust,no_run,ignore
96
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
97
/// # extern crate ext_php_rs;
98
/// use ext_php_rs::prelude::*;
99
///
100
/// #[php_class]
101
/// pub struct Human {
102
///     name: String,
103
///     age: i32,
104
///     #[php(prop)]
105
///     address: String,
106
/// }
107
///
108
/// #[php_module]
109
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
110
///     module.class::<Human>()
111
/// }
112
/// # fn main() {}
113
/// ```
114
///
115
/// Create a custom exception `RedisException`, which extends `Exception`, and
116
/// put it in the `Redis\Exception` namespace:
117
///
118
/// ```rust,no_run,ignore
119
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
120
/// # extern crate ext_php_rs;
121
/// use ext_php_rs::{
122
///     prelude::*,
123
///     exception::PhpException,
124
///     zend::ce
125
/// };
126
///
127
/// #[php_class]
128
/// #[php(name = "Redis\\Exception\\RedisException")]
129
/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
130
/// #[derive(Default)]
131
/// pub struct RedisException;
132
///
133
/// // Throw our newly created exception
134
/// #[php_function]
135
/// pub fn throw_exception() -> PhpResult<i32> {
136
///     Err(PhpException::from_class::<RedisException>("Not good!".into()))
137
/// }
138
///
139
/// #[php_module]
140
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
141
///     module
142
///         .class::<RedisException>()
143
///         .function(wrap_function!(throw_exception))
144
/// }
145
/// # fn main() {}
146
/// ```
147
///
148
/// ## Implementing an Interface
149
///
150
/// To implement an interface, use `#[php(implements(ce = ce_fn, stub =
151
/// "InterfaceName")]` where `ce_fn` is an function returning a `ClassEntry`. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
152
///
153
/// ````rust,no_run,ignore
154
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
155
/// # extern crate ext_php_rs;
156
/// use ext_php_rs::{
157
///     prelude::*,
158
///     exception::PhpResult,
159
///     types::Zval,
160
///     zend::ce,
161
/// };
162
///
163
/// #[php_class]
164
/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
165
/// #[derive(Default)]
166
/// pub struct EvenNumbersArray;
167
///
168
/// /// Returns `true` if the array offset is an even number.
169
/// /// Usage:
170
/// /// ```php
171
/// /// $arr = new EvenNumbersArray();
172
/// /// var_dump($arr[0]); // true
173
/// /// var_dump($arr[1]); // false
174
/// /// var_dump($arr[2]); // true
175
/// /// var_dump($arr[3]); // false
176
/// /// var_dump($arr[4]); // true
177
/// /// var_dump($arr[5] = true); // Fatal error:  Uncaught Exception: Setting values is not supported
178
/// /// ```
179
/// #[php_impl]
180
/// impl EvenNumbersArray {
181
///     pub fn __construct() -> EvenNumbersArray {
182
///         EvenNumbersArray {}
183
///     }
184
///     // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
185
///     pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
186
///         offset.is_long()
187
///     }
188
///     pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
189
///         let integer_offset = offset.long().ok_or("Expected integer offset")?;
190
///         Ok(integer_offset % 2 == 0)
191
///     }
192
///     pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
193
///         Err("Setting values is not supported".into())
194
///     }
195
///     pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
196
///         Err("Setting values is not supported".into())
197
///     }
198
/// }
199
///
200
/// #[php_module]
201
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
202
///     module.class::<EvenNumbersArray>()
203
/// }
204
/// # fn main() {}
205
/// ````
206
// END DOCS FROM classes.md
207
#[proc_macro_attribute]
208
pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
×
209
    php_class_internal(args.into(), input.into()).into()
×
210
}
211

212
#[allow(clippy::needless_pass_by_value)]
213
fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1✔
214
    let input = parse_macro_input2!(input as ItemStruct);
2✔
215
    if !args.is_empty() {
216
        return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
217
    }
218

219
    class::parser(input).unwrap_or_else(|e| e.to_compile_error())
3✔
220
}
221

222
// BEGIN DOCS FROM enum.md
223
/// # `#[php_enum]` Attribute
224
///
225
/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute
226
/// macro. This attribute derives the `RegisteredClass` and `PhpEnum` traits on
227
/// your enum. To register the enum use the `enumeration::<EnumName>()` method
228
/// on the `ModuleBuilder` in the `#[php_module]` macro.
229
///
230
/// ## Options
231
///
232
/// The `#[php_enum]` attribute can be configured with the following options:
233
/// - `#[php(name = "EnumName")]` or `#[php(change_case = snake_case)]`: Sets
234
///   the name of the enum in PHP. The default is the `PascalCase` name of the
235
///   enum.
236
/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust
237
///   discriminants (e.g., `Hearts = 1`).
238
///
239
/// The cases of the enum can be configured with the following options:
240
/// - `#[php(name = "CaseName")]` or `#[php(change_case = snake_case)]`: Sets
241
///   the name of the enum case in PHP. The default is the `PascalCase` name of
242
///   the case.
243
/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant
244
///   value for the enum case. This can be a string or an integer. If not set,
245
///   the case will be exported as a simple enum case without a discriminant.
246
///
247
/// ### Example
248
///
249
/// This example creates a PHP enum `Suit`.
250
///
251
/// ```rust,no_run,ignore
252
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
253
/// # extern crate ext_php_rs;
254
/// use ext_php_rs::prelude::*;
255
///
256
/// #[php_enum]
257
/// pub enum Suit {
258
///     Hearts,
259
///     Diamonds,
260
///     Clubs,
261
///     Spades,
262
/// }
263
///
264
/// #[php_module]
265
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
266
///     module.enumeration::<Suit>()
267
/// }
268
/// # fn main() {}
269
/// ```
270
///
271
/// ## Backed Enums
272
/// Enums can also be backed by either `i64` or `&'static str`. Those values can
273
/// be set using the `#[php(value = "value")]` or `#[php(value = 123)]`
274
/// attributes on the enum variants.
275
///
276
/// All variants must have a value of the same type, either all `i64` or all
277
/// `&'static str`.
278
///
279
/// ```rust,no_run,ignore
280
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
281
/// # extern crate ext_php_rs;
282
/// use ext_php_rs::prelude::*;
283
///
284
/// #[php_enum]
285
/// pub enum Suit {
286
///     #[php(value = "hearts")]
287
///     Hearts,
288
///     #[php(value = "diamonds")]
289
///     Diamonds,
290
///     #[php(value = "clubs")]
291
///     Clubs,
292
///     #[php(value = "spades")]
293
///     Spades,
294
/// }
295
/// #[php_module]
296
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
297
///     module.enumeration::<Suit>()
298
/// }
299
/// # fn main() {}
300
/// ```
301
///
302
/// ### 'Native' Discriminators
303
/// Native rust discriminants are currently not supported and will not be
304
/// exported to PHP.
305
///
306
/// To avoid confusion a compiler error will be raised if you try to use a
307
/// native discriminant. You can ignore this error by adding the
308
/// `#[php(allow_native_discriminants)]` attribute to your enum.
309
///
310
/// ```rust,no_run,ignore
311
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
312
/// # extern crate ext_php_rs;
313
/// use ext_php_rs::prelude::*;
314
///
315
/// #[php_enum]
316
/// #[php(allow_native_discriminants)]
317
/// pub enum Suit {
318
///     Hearts = 1,
319
///     Diamonds = 2,
320
///     Clubs = 3,
321
///     Spades = 4,
322
/// }
323
///
324
/// #[php_module]
325
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
326
///     module.enumeration::<Suit>()
327
/// }
328
/// # fn main() {}
329
/// ```
330
///
331
///
332
/// TODO: Add backed enums example
333
// END DOCS FROM enum.md
334
#[proc_macro_attribute]
335
pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
×
336
    php_enum_internal(args.into(), input.into()).into()
×
337
}
338

339
fn php_enum_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
3✔
340
    let input = parse_macro_input2!(input as ItemEnum);
6✔
341

342
    enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
343
}
344

345
// BEGIN DOCS FROM interface.md
346
/// # `#[php_interface]` Attribute
347
///
348
/// You can export a `Trait` block to PHP. This exports all methods as well as
349
/// constants to PHP on the interface. Trait method SHOULD NOT contain default
350
/// implementations, as these are not supported in PHP interfaces.
351
///
352
/// ## Options
353
///
354
/// By default all constants are renamed to `UPPER_CASE` and all methods are
355
/// renamed to `camelCase`. This can be changed by passing the
356
/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
357
/// the `impl` block. The options are:
358
///
359
/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
360
///   case.
361
/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
362
///   snake case.
363
///
364
/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
365
/// for a list of all available cases.
366
///
367
/// ## Methods
368
///
369
/// See the [`php_impl`](./impl.md#)
370
///
371
/// ## Constants
372
///
373
/// See the [`php_impl`](./impl.md#)
374
///
375
/// ## Example
376
///
377
/// Define an example trait with methods and constant:
378
///
379
/// ```rust,no_run,ignore
380
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
381
/// # extern crate ext_php_rs;
382
/// use ext_php_rs::{prelude::*, types::ZendClassObject};
383
///
384
///
385
/// #[php_interface]
386
/// #[php(name = "Rust\\TestInterface")]
387
/// trait Test {
388
///     const TEST: &'static str = "TEST";
389
///
390
///     fn co();
391
///
392
///     #[php(defaults(value = 0))]
393
///     fn set_value(&mut self, value: i32);
394
/// }
395
///
396
/// #[php_module]
397
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
398
///     module
399
///         .interface::<PhpInterfaceTest>()
400
/// }
401
///
402
/// # fn main() {}
403
/// ```
404
///
405
/// Using our newly created interface in PHP:
406
///
407
/// ```php
408
/// <?php
409
///
410
/// assert(interface_exists("Rust\TestInterface"));
411
///
412
/// class B implements Rust\TestInterface {
413
///
414
///     public static function co() {}
415
///
416
///     public function setValue(?int $value = 0) {
417
///
418
///     }
419
/// }
420
/// ```
421
// END DOCS FROM interface.md
422
#[proc_macro_attribute]
NEW
423
pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
×
NEW
424
    php_interface_internal(args.into(), input.into()).into()
×
425
}
426

427
fn php_interface_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2✔
428
    let input = parse_macro_input2!(input as ItemTrait);
4✔
429

NEW
430
    interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
431
}
432

433
// BEGIN DOCS FROM function.md
434
/// # `#[php_function]` Attribute
435
///
436
/// Used to annotate functions which should be exported to PHP. Note that this
437
/// should not be used on class methods - see the `#[php_impl]` macro for that.
438
///
439
/// See the [list of types](../types/index.md) that are valid as parameter and
440
/// return types.
441
///
442
/// ## Optional parameters
443
///
444
/// Optional parameters can be used by setting the Rust parameter type to a
445
/// variant of `Option<T>`. The macro will then figure out which parameters are
446
/// optional by using the last consecutive arguments that are a variant of
447
/// `Option<T>` or have a default value.
448
///
449
/// ```rust,no_run,ignore
450
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
451
/// # extern crate ext_php_rs;
452
/// use ext_php_rs::prelude::*;
453
///
454
/// #[php_function]
455
/// pub fn greet(name: String, age: Option<i32>) -> String {
456
///     let mut greeting = format!("Hello, {}!", name);
457
///
458
///     if let Some(age) = age {
459
///         greeting += &format!(" You are {} years old.", age);
460
///     }
461
///
462
///     greeting
463
/// }
464
///
465
/// #[php_module]
466
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
467
///     module.function(wrap_function!(greet))
468
/// }
469
/// # fn main() {}
470
/// ```
471
///
472
/// Default parameter values can also be set for optional parameters. This is
473
/// done through the `#[php(defaults)]` attribute option. When an optional
474
/// parameter has a default, it does not need to be a variant of `Option`:
475
///
476
/// ```rust,no_run,ignore
477
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
478
/// # extern crate ext_php_rs;
479
/// use ext_php_rs::prelude::*;
480
///
481
/// #[php_function]
482
/// #[php(defaults(offset = 0))]
483
/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
484
///     let haystack: String = haystack.chars().skip(offset as usize).collect();
485
///     haystack.find(needle)
486
/// }
487
///
488
/// #[php_module]
489
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
490
///     module.function(wrap_function!(rusty_strpos))
491
/// }
492
/// # fn main() {}
493
/// ```
494
///
495
/// Note that if there is a non-optional argument after an argument that is a
496
/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
497
/// argument rather than an optional argument.
498
///
499
/// ```rust,no_run,ignore
500
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
501
/// # extern crate ext_php_rs;
502
/// use ext_php_rs::prelude::*;
503
///
504
/// /// `age` will be deemed required and nullable rather than optional.
505
/// #[php_function]
506
/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
507
///     let mut greeting = format!("Hello, {}!", name);
508
///
509
///     if let Some(age) = age {
510
///         greeting += &format!(" You are {} years old.", age);
511
///     }
512
///
513
///     greeting += &format!(" {}.", description);
514
///     greeting
515
/// }
516
///
517
/// #[php_module]
518
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
519
///     module.function(wrap_function!(greet))
520
/// }
521
/// # fn main() {}
522
/// ```
523
///
524
/// You can also specify the optional arguments if you want to have nullable
525
/// arguments before optional arguments. This is done through an attribute
526
/// parameter:
527
///
528
/// ```rust,no_run,ignore
529
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
530
/// # extern crate ext_php_rs;
531
/// use ext_php_rs::prelude::*;
532
///
533
/// /// `age` will be deemed required and nullable rather than optional,
534
/// /// while description will be optional.
535
/// #[php_function]
536
/// #[php(optional = "description")]
537
/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
538
///     let mut greeting = format!("Hello, {}!", name);
539
///
540
///     if let Some(age) = age {
541
///         greeting += &format!(" You are {} years old.", age);
542
///     }
543
///
544
///     if let Some(description) = description {
545
///         greeting += &format!(" {}.", description);
546
///     }
547
///
548
///     greeting
549
/// }
550
///
551
/// #[php_module]
552
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
553
///     module.function(wrap_function!(greet))
554
/// }
555
/// # fn main() {}
556
/// ```
557
///
558
/// ## Variadic Functions
559
///
560
/// Variadic functions can be implemented by specifying the last argument in the
561
/// Rust function to the type `&[&Zval]`. This is the equivalent of a PHP
562
/// function using the `...$args` syntax.
563
///
564
/// ```rust,no_run,ignore
565
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
566
/// # extern crate ext_php_rs;
567
/// use ext_php_rs::{prelude::*, types::Zval};
568
///
569
/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
570
/// #[php_function]
571
/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
572
///     // numbers is a slice of 4 Zvals all of type long
573
///     number
574
/// }
575
///
576
/// #[php_module]
577
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
578
///     module.function(wrap_function!(add))
579
/// }
580
/// # fn main() {}
581
/// ```
582
///
583
/// ## Returning `Result<T, E>`
584
///
585
/// You can also return a `Result` from the function. The error variant will be
586
/// translated into an exception and thrown. See the section on
587
/// [exceptions](../exceptions.md) for more details.
588
// END DOCS FROM function.md
589
#[proc_macro_attribute]
590
pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
×
591
    php_function_internal(args.into(), input.into()).into()
×
592
}
593

594
#[allow(clippy::needless_pass_by_value)]
595
fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
596
    let input = parse_macro_input2!(input as ItemFn);
×
597
    if !args.is_empty() {
598
        return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
599
    }
600

601
    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
602
}
603

604
// BEGIN DOCS FROM constant.md
605
/// # `#[php_const]` Attribute
606
///
607
/// Exports a Rust constant as a global PHP constant. The constant can be any
608
/// type that implements `IntoConst`.
609
///
610
/// The `wrap_constant!()` macro can be used to simplify the registration of
611
/// constants. It sets the name and doc comments for the constant.
612
///
613
/// You can rename the const with options:
614
///
615
/// - `name` - Allows you to rename the property, e.g. `#[php(name =
616
///   "new_name")]`
617
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
618
///   `#[php(change_case = PascalCase)]`
619
///
620
/// ## Examples
621
///
622
/// ```rust,no_run,ignore
623
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
624
/// # extern crate ext_php_rs;
625
/// use ext_php_rs::prelude::*;
626
///
627
/// #[php_const]
628
/// const TEST_CONSTANT: i32 = 100;
629
///
630
/// #[php_const]
631
/// #[php(name = "I_AM_RENAMED")]
632
/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
633
///
634
/// #[php_const]
635
/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
636
///
637
/// #[php_module]
638
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
639
///     module
640
///         .constant(wrap_constant!(TEST_CONSTANT))
641
///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
642
///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
643
/// }
644
/// # fn main() {}
645
/// ```
646
///
647
/// ## PHP usage
648
///
649
/// ```php
650
/// <?php
651
///
652
/// var_dump(TEST_CONSTANT); // int(100)
653
/// var_dump(I_AM_RENAMED); // int(42)
654
/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
655
/// ```
656
// END DOCS FROM constant.md
657
#[proc_macro_attribute]
658
pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
×
659
    php_const_internal(args.into(), input.into()).into()
×
660
}
661

662
#[allow(clippy::needless_pass_by_value)]
663
fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1✔
664
    let input = parse_macro_input2!(input as ItemConst);
2✔
665
    if !args.is_empty() {
666
        return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
667
    }
668

669
    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
3✔
670
}
671

672
// BEGIN DOCS FROM module.md
673
/// # `#[php_module]` Attribute
674
///
675
/// The module macro is used to annotate the `get_module` function, which is
676
/// used by the PHP interpreter to retrieve information about your extension,
677
/// including the name, version, functions and extra initialization functions.
678
/// Regardless if you use this macro, your extension requires a `extern "C" fn
679
/// get_module()` so that PHP can get this information.
680
///
681
/// The function is renamed to `get_module` if you have used another name. The
682
/// function is passed an instance of `ModuleBuilder` which allows you to
683
/// register the following (if required):
684
///
685
/// - Functions, classes, and constants
686
/// - Extension and request startup and shutdown functions.
687
///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
688
/// - PHP extension information function
689
///   - Used by the `phpinfo()` function to get information about your
690
///     extension.
691
///
692
/// Classes and constants are not registered with PHP in the `get_module`
693
/// function. These are registered inside the extension startup function.
694
///
695
/// ## Usage
696
///
697
/// ```rust,no_run,ignore
698
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
699
/// # extern crate ext_php_rs;
700
/// use ext_php_rs::{
701
///     prelude::*,
702
///     zend::ModuleEntry,
703
///     info_table_start,
704
///     info_table_row,
705
///     info_table_end
706
/// };
707
///
708
/// #[php_const]
709
/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
710
///
711
/// #[php_class]
712
/// pub struct Test {
713
///     a: i32,
714
///     b: i32
715
/// }
716
/// #[php_function]
717
/// pub fn hello_world() -> &'static str {
718
///     "Hello, world!"
719
/// }
720
///
721
/// /// Used by the `phpinfo()` function and when you run `php -i`.
722
/// /// This will probably be simplified with another macro eventually!
723
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
724
///     info_table_start!();
725
///     info_table_row!("my extension", "enabled");
726
///     info_table_end!();
727
/// }
728
///
729
/// #[php_module]
730
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
731
///     module
732
///         .constant(wrap_constant!(MY_CUSTOM_CONST))
733
///         .class::<Test>()
734
///         .function(wrap_function!(hello_world))
735
///         .info_function(php_module_info)
736
/// }
737
/// # fn main() {}
738
/// ```
739
// END DOCS FROM module.md
740
#[proc_macro_attribute]
741
pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
×
742
    php_module_internal(args.into(), input.into()).into()
×
743
}
744

745
#[allow(clippy::needless_pass_by_value)]
746
fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
747
    let input = parse_macro_input2!(input as ItemFn);
×
748
    if !args.is_empty() {
749
        return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
750
    }
751

752
    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
753
}
754

755
// BEGIN DOCS FROM impl.md
756
/// # `#[php_impl]` Attribute
757
///
758
/// You can export an entire `impl` block to PHP. This exports all methods as
759
/// well as constants to PHP on the class that it is implemented on. This
760
/// requires the `#[php_class]` macro to already be used on the underlying
761
/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
762
/// block can be exported per class.
763
///
764
/// If you do not want a function exported to PHP, you should place it in a
765
/// separate `impl` block.
766
///
767
/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
768
/// &raquo;](./async_impl.md) for more info.
769
///
770
/// ## Options
771
///
772
/// By default all constants are renamed to `UPPER_CASE` and all methods are
773
/// renamed to camelCase. This can be changed by passing the
774
/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
775
/// the `impl` block. The options are:
776
///
777
/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
778
///   case.
779
/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
780
///   snake case.
781
///
782
/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
783
/// for a list of all available cases.
784
///
785
/// ## Methods
786
///
787
/// Methods basically follow the same rules as functions, so read about the
788
/// [`php_function`] macro first. The primary difference between functions and
789
/// methods is they are bounded by their class object.
790
///
791
/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
792
/// a consuming `self` parameter. Static methods can omit this `self` parameter.
793
///
794
/// To access the underlying Zend object, you can take a reference to a
795
/// `ZendClassObject<T>` in place of the self parameter, where the parameter
796
/// must be named `self_`. This can also be used to return a reference to
797
/// `$this`.
798
///
799
/// The rest of the options are passed as separate attributes:
800
///
801
/// - `#[php(defaults(i = 5, b = "hello"))]` - Sets the default value for
802
///   parameter(s).
803
/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
804
///   also sets the remaining parameters as optional, so all optional parameters
805
///   must be a variant of `Option<T>`.
806
/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
807
///   "private")]` - Sets the visibility of the method.
808
/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
809
///   identifier, without renaming the Rust method name.
810
///
811
/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
812
/// the equivalent function attribute parameters.
813
///
814
/// ### Constructors
815
///
816
/// By default, if a class does not have a constructor, it is not constructable
817
/// from PHP. It can only be returned from a Rust function to PHP.
818
///
819
/// Constructors are Rust methods which can take any amount of parameters and
820
/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
821
/// When the error variant of `Result` is encountered, it is thrown as an
822
/// exception and the class is not constructed.
823
///
824
/// Constructors are designated by either naming the method `__construct` or by
825
/// annotating a method with the `#[php(constructor)]` attribute. Note that when
826
/// using the attribute, the function is not exported to PHP like a regular
827
/// method.
828
///
829
/// Constructors cannot use the visibility or rename attributes listed above.
830
///
831
/// ## Constants
832
///
833
/// Constants are defined as regular Rust `impl` constants. Any type that
834
/// implements `IntoZval` can be used as a constant. Constant visibility is not
835
/// supported at the moment, and therefore no attributes are valid on constants.
836
///
837
/// ## Property getters and setters
838
///
839
/// You can add properties to classes which use Rust functions as getters and/or
840
/// setters. This is done with the `#[php(getter)]` and `#[php(setter)]`
841
/// attributes. By default, the `get_` or `set_` prefix is trimmed from the
842
/// start of the function name, and the remainder is used as the property name.
843
///
844
/// If you want to use a different name for the property, you can pass a `name`
845
/// or `change_case` option to the `#[php]` attribute which will change the
846
/// property name.
847
///
848
/// Properties do not necessarily have to have both a getter and a setter, if
849
/// the property is immutable the setter can be omitted, and vice versa for
850
/// getters.
851
///
852
/// The `#[php(getter)]` and `#[php(setter)]` attributes are mutually exclusive
853
/// on methods. Properties cannot have multiple getters or setters, and the
854
/// property name cannot conflict with field properties defined on the struct.
855
///
856
/// As the same as field properties, method property types must implement both
857
/// `IntoZval` and `FromZval`.
858
///
859
/// ## Example
860
///
861
/// Continuing on from our `Human` example in the structs section, we will
862
/// define a constructor, as well as getters for the properties. We will also
863
/// define a constant for the maximum age of a `Human`.
864
///
865
/// ```rust,no_run,ignore
866
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
867
/// # extern crate ext_php_rs;
868
/// use ext_php_rs::{prelude::*, types::ZendClassObject};
869
///
870
/// #[php_class]
871
/// #[derive(Debug, Default)]
872
/// pub struct Human {
873
///     name: String,
874
///     age: i32,
875
///     #[php(prop)]
876
///     address: String,
877
/// }
878
///
879
/// #[php_impl]
880
/// impl Human {
881
///     const MAX_AGE: i32 = 100;
882
///
883
///     // No `#[constructor]` attribute required here - the name is `__construct`.
884
///     pub fn __construct(name: String, age: i32) -> Self {
885
///         Self {
886
///             name,
887
///             age,
888
///             address: String::new()
889
///         }
890
///     }
891
///
892
///     #[php(getter)]
893
///     pub fn get_name(&self) -> String {
894
///         self.name.to_string()
895
///     }
896
///
897
///     #[php(setter)]
898
///     pub fn set_name(&mut self, name: String) {
899
///         self.name = name;
900
///     }
901
///
902
///     #[php(getter)]
903
///     pub fn get_age(&self) -> i32 {
904
///         self.age
905
///     }
906
///
907
///     pub fn introduce(&self) {
908
///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
909
///     }
910
///
911
///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
912
///         dbg!(self_)
913
///     }
914
///
915
///     pub fn get_max_age() -> i32 {
916
///         Self::MAX_AGE
917
///     }
918
/// }
919
/// #[php_module]
920
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
921
///     module.class::<Human>()
922
/// }
923
/// # fn main() {}
924
/// ```
925
///
926
/// Using our newly created class in PHP:
927
///
928
/// ```php
929
/// <?php
930
///
931
/// $me = new Human('David', 20);
932
///
933
/// $me->introduce(); // My name is David and I am 20 years old.
934
/// var_dump(Human::get_max_age()); // int(100)
935
/// var_dump(Human::MAX_AGE); // int(100)
936
/// ```
937
///
938
/// [`php_async_impl`]: ./async_impl.md
939
// END DOCS FROM impl.md
940
#[proc_macro_attribute]
941
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
×
942
    php_impl_internal(args.into(), input.into()).into()
×
943
}
944

945
#[allow(clippy::needless_pass_by_value)]
946
fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
947
    let input = parse_macro_input2!(input as ItemImpl);
×
948
    if !args.is_empty() {
949
        return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
950
    }
951

952
    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
953
}
954

955
// BEGIN DOCS FROM extern.md
956
/// # `#[php_extern]` Attribute
957
///
958
/// Attribute used to annotate `extern` blocks which are deemed as PHP
959
/// functions.
960
///
961
/// This allows you to 'import' PHP functions into Rust so that they can be
962
/// called like regular Rust functions. Parameters can be any type that
963
/// implements [`IntoZval`], and the return type can be anything that implements
964
/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
965
/// case).
966
///
967
/// Unlike most other attributes, this does not need to be placed inside a
968
/// `#[php_module]` block.
969
///
970
/// # Panics
971
///
972
/// The function can panic when called under a few circumstances:
973
///
974
/// * The function could not be found or was not callable.
975
/// * One of the parameters could not be converted into a [`Zval`].
976
/// * The actual function call failed internally.
977
/// * The output [`Zval`] could not be parsed into the output type.
978
///
979
/// The last point can be important when interacting with functions that return
980
/// unions, such as [`strpos`] which can return an integer or a boolean. In this
981
/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
982
/// invalid, and vice versa.
983
///
984
/// # Example
985
///
986
/// This `extern` block imports the [`strpos`] function from PHP. Notice that
987
/// the string parameters can take either [`String`] or [`&str`], the optional
988
/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
989
/// as the return type is an integer-boolean union.
990
///
991
/// ```rust,no_run,ignore
992
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
993
/// # extern crate ext_php_rs;
994
/// use ext_php_rs::{
995
///     prelude::*,
996
///     types::Zval,
997
/// };
998
///
999
/// #[php_extern]
1000
/// extern "C" {
1001
///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
1002
/// }
1003
///
1004
/// #[php_function]
1005
/// pub fn my_strpos() {
1006
///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
1007
/// }
1008
///
1009
/// #[php_module]
1010
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1011
///     module.function(wrap_function!(my_strpos))
1012
/// }
1013
/// # fn main() {}
1014
/// ```
1015
///
1016
/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
1017
/// [`IntoZval`]: crate::convert::IntoZval
1018
/// [`Zval`]: crate::types::Zval
1019
// END DOCS FROM extern.md
1020
#[proc_macro_attribute]
1021
pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
×
1022
    php_extern_internal(args.into(), input.into()).into()
×
1023
}
1024

1025
#[allow(clippy::needless_pass_by_value)]
1026
fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
1027
    let input = parse_macro_input2!(input as ItemForeignMod);
×
1028

1029
    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
1030
}
1031

1032
// BEGIN DOCS FROM zval_convert.md
1033
/// # `ZvalConvert` Derive Macro
1034
///
1035
/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
1036
/// traits on a struct or enum.
1037
///
1038
/// ## Structs
1039
///
1040
/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
1041
/// also implemented, mapping fields to properties in both directions. All
1042
/// fields on the struct must implement `FromZval` as well. Generics are allowed
1043
/// on structs that use the derive macro, however, the implementation will add a
1044
/// `FromZval` bound to all generics types.
1045
///
1046
/// ### Examples
1047
///
1048
/// ```rust,no_run,ignore
1049
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1050
/// # extern crate ext_php_rs;
1051
/// use ext_php_rs::prelude::*;
1052
///
1053
/// #[derive(ZvalConvert)]
1054
/// pub struct ExampleClass<'a> {
1055
///     a: i32,
1056
///     b: String,
1057
///     c: &'a str
1058
/// }
1059
///
1060
/// #[php_function]
1061
/// pub fn take_object(obj: ExampleClass) {
1062
///     dbg!(obj.a, obj.b, obj.c);
1063
/// }
1064
///
1065
/// #[php_function]
1066
/// pub fn give_object() -> ExampleClass<'static> {
1067
///     ExampleClass {
1068
///         a: 5,
1069
///         b: "String".to_string(),
1070
///         c: "Borrowed",
1071
///     }
1072
/// }
1073
///
1074
/// #[php_module]
1075
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1076
///     module
1077
///         .function(wrap_function!(take_object))
1078
///         .function(wrap_function!(give_object))
1079
/// }
1080
/// # fn main() {}
1081
/// ```
1082
///
1083
/// Calling from PHP:
1084
///
1085
/// ```php
1086
/// <?php
1087
///
1088
/// $obj = new stdClass;
1089
/// $obj->a = 5;
1090
/// $obj->b = 'Hello, world!';
1091
/// $obj->c = 'another string';
1092
///
1093
/// take_object($obj);
1094
/// var_dump(give_object());
1095
/// ```
1096
///
1097
/// Another example involving generics:
1098
///
1099
/// ```rust,no_run,ignore
1100
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1101
/// # extern crate ext_php_rs;
1102
/// use ext_php_rs::prelude::*;
1103
///
1104
/// // T must implement both `PartialEq<i32>` and `FromZval`.
1105
/// #[derive(Debug, ZvalConvert)]
1106
/// pub struct CompareVals<T: PartialEq<i32>> {
1107
///     a: T,
1108
///     b: T
1109
/// }
1110
///
1111
/// #[php_function]
1112
/// pub fn take_object(obj: CompareVals<i32>) {
1113
///     dbg!(obj);
1114
/// }
1115
///
1116
/// #[php_module]
1117
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1118
///     module
1119
///         .function(wrap_function!(take_object))
1120
/// }
1121
/// # fn main() {}
1122
/// ```
1123
///
1124
/// ## Enums
1125
///
1126
/// When used on an enum, the `FromZval` implementation will treat the enum as a
1127
/// tagged union with a mixed datatype. This allows you to accept multiple types
1128
/// in a parameter, for example, a string and an integer.
1129
///
1130
/// The enum variants must not have named fields, and each variant must have
1131
/// exactly one field (the type to extract from the zval). Optionally, the enum
1132
/// may have one default variant with no data contained, which will be used when
1133
/// the rest of the variants could not be extracted from the zval.
1134
///
1135
/// The ordering of the variants in the enum is important, as the `FromZval`
1136
/// implementation will attempt to parse the zval data in order. For example, if
1137
/// you put a `String` variant before an integer variant, the integer would be
1138
/// converted to a string and passed as the string variant.
1139
///
1140
/// ### Examples
1141
///
1142
/// Basic example showing the importance of variant ordering and default field:
1143
///
1144
/// ```rust,no_run,ignore
1145
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1146
/// # extern crate ext_php_rs;
1147
/// use ext_php_rs::prelude::*;
1148
///
1149
/// #[derive(Debug, ZvalConvert)]
1150
/// pub enum UnionExample<'a> {
1151
///     Long(u64), // Long
1152
///     ProperStr(&'a str), // Actual string - not a converted value
1153
///     ParsedStr(String), // Potentially parsed string, i.e. a double
1154
///     None // Zval did not contain anything that could be parsed above
1155
/// }
1156
///
1157
/// #[php_function]
1158
/// pub fn test_union(val: UnionExample) {
1159
///     dbg!(val);
1160
/// }
1161
///
1162
/// #[php_function]
1163
/// pub fn give_union() -> UnionExample<'static> {
1164
///     UnionExample::Long(5)
1165
/// }
1166
///
1167
/// #[php_module]
1168
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1169
///     module
1170
///         .function(wrap_function!(test_union))
1171
///         .function(wrap_function!(give_union))
1172
/// }
1173
/// # fn main() {}
1174
/// ```
1175
///
1176
/// Use in PHP:
1177
///
1178
/// ```php
1179
/// test_union(5); // UnionExample::Long(5)
1180
/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
1181
/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
1182
/// test_union(null); // UnionExample::None
1183
/// var_dump(give_union()); // int(5)
1184
/// ```
1185
// END DOCS FROM zval_convert.md
1186
#[proc_macro_derive(ZvalConvert)]
1187
pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
×
1188
    zval_convert_derive_internal(input.into()).into()
×
1189
}
1190

1191
fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
×
1192
    let input = parse_macro_input2!(input as DeriveInput);
×
1193

1194
    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
1195
}
1196

1197
/// Defines an `extern` function with the Zend fastcall convention based on
1198
/// operating system.
1199
///
1200
/// On Windows, Zend fastcall functions use the vector calling convention, while
1201
/// on all other operating systems no fastcall convention is used (just the
1202
/// regular C calling convention).
1203
///
1204
/// This macro wraps a function and applies the correct calling convention.
1205
///
1206
/// ## Examples
1207
///
1208
/// ```rust,ignore
1209
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1210
/// use ext_php_rs::zend_fastcall;
1211
///
1212
/// zend_fastcall! {
1213
///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
1214
///         a + b
1215
///     }
1216
/// }
1217
/// ```
1218
///
1219
/// On Windows, this function will have the signature `pub extern "vectorcall"
1220
/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
1221
/// signature `pub extern "C" fn(i32, i32) -> i32`.
1222
///
1223
/// ## Support
1224
///
1225
/// The `vectorcall` ABI is currently only supported on Windows with nightly
1226
/// Rust and the `abi_vectorcall` feature enabled.
1227
#[proc_macro]
1228
pub fn zend_fastcall(input: TokenStream) -> TokenStream {
×
1229
    zend_fastcall_internal(input.into()).into()
×
1230
}
1231

1232
fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
×
1233
    let input = parse_macro_input2!(input as ItemFn);
×
1234

1235
    fastcall::parser(input)
1236
}
1237

1238
/// Wraps a function to be used in the [`Module::function`] method.
1239
#[proc_macro]
1240
pub fn wrap_function(input: TokenStream) -> TokenStream {
×
1241
    wrap_function_internal(input.into()).into()
×
1242
}
1243

1244
fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
×
1245
    let input = parse_macro_input2!(input as syn::Path);
×
1246

1247
    match function::wrap(&input) {
1248
        Ok(parsed) => parsed,
×
1249
        Err(e) => e.to_compile_error(),
×
1250
    }
1251
}
1252

1253
/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
1254
#[proc_macro]
1255
pub fn wrap_constant(input: TokenStream) -> TokenStream {
×
1256
    wrap_constant_internal(input.into()).into()
×
1257
}
1258

1259
fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1✔
1260
    let input = parse_macro_input2!(input as syn::Path);
2✔
1261

1262
    match constant::wrap(&input) {
1263
        Ok(parsed) => parsed,
1✔
1264
        Err(e) => e.to_compile_error(),
×
1265
    }
1266
}
1267

1268
macro_rules! parse_macro_input2 {
1269
    ($tokenstream:ident as $ty:ty) => {
1270
        match syn::parse2::<$ty>($tokenstream) {
1271
            Ok(data) => data,
1272
            Err(err) => {
1273
                return proc_macro2::TokenStream::from(err.to_compile_error());
1274
            }
1275
        }
1276
    };
1277
    ($tokenstream:ident) => {
1278
        $crate::parse_macro_input!($tokenstream as _)
1279
    };
1280
}
1281

1282
pub(crate) use parse_macro_input2;
1283

1284
macro_rules! err {
1285
    ($span:expr => $($msg:tt)*) => {
1286
        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
1287
    };
1288
    ($($msg:tt)*) => {
1289
        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
1290
    };
1291
}
1292

1293
/// Bails out of a function with a syn error.
1294
macro_rules! bail {
1295
    ($span:expr => $($msg:tt)*) => {
1296
        return Err($crate::err!($span => $($msg)*))
1297
    };
1298
    ($($msg:tt)*) => {
1299
        return Err($crate::err!($($msg)*))
1300
    };
1301
}
1302

1303
pub(crate) use bail;
1304
pub(crate) use err;
1305

1306
pub(crate) mod prelude {
1307
    pub(crate) trait OptionTokens {
1308
        fn option_tokens(&self) -> proc_macro2::TokenStream;
1309
    }
1310

1311
    impl<T: quote::ToTokens> OptionTokens for Option<T> {
1312
        fn option_tokens(&self) -> proc_macro2::TokenStream {
3✔
1313
            if let Some(token) = self {
3✔
1314
                quote::quote! { ::std::option::Option::Some(#token) }
×
1315
            } else {
1316
                quote::quote! { ::std::option::Option::None }
3✔
1317
            }
1318
        }
1319
    }
1320

1321
    pub(crate) use crate::{bail, err};
1322
    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
1323
}
1324

1325
#[cfg(test)]
1326
mod tests {
1327
    use super::*;
1328
    use std::path::PathBuf;
1329

1330
    type AttributeFn =
1331
        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1332
    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1333

1334
    #[test]
1335
    pub fn test_expand() {
1336
        macrotest::expand("tests/expand/*.rs");
1337
        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1338
            let entry = entry.expect("Failed to read expand test file");
1339
            runtime_expand_attr(&entry);
1340
            runtime_expand_func(&entry);
1341
            runtime_expand_derive(&entry);
1342
        }
1343
    }
1344

1345
    fn runtime_expand_attr(path: &PathBuf) {
1346
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1347
        runtime_macros::emulate_attributelike_macro_expansion(
1348
            file,
1349
            &[
1350
                ("php_class", php_class_internal as AttributeFn),
1351
                ("php_const", php_const_internal as AttributeFn),
1352
                ("php_enum", php_enum_internal as AttributeFn),
1353
                ("php_interface", php_interface_internal as AttributeFn),
1354
                ("php_extern", php_extern_internal as AttributeFn),
1355
                ("php_function", php_function_internal as AttributeFn),
1356
                ("php_impl", php_impl_internal as AttributeFn),
1357
                ("php_module", php_module_internal as AttributeFn),
1358
            ],
1359
        )
1360
        .expect("Failed to expand attribute macros in test file");
1361
    }
1362

1363
    fn runtime_expand_func(path: &PathBuf) {
1364
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1365
        runtime_macros::emulate_functionlike_macro_expansion(
1366
            file,
1367
            &[
1368
                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1369
                ("wrap_function", wrap_function_internal as FunctionLikeFn),
1370
                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1371
            ],
1372
        )
1373
        .expect("Failed to expand function-like macros in test file");
1374
    }
1375

1376
    fn runtime_expand_derive(path: &PathBuf) {
1377
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1378
        runtime_macros::emulate_derive_macro_expansion(
1379
            file,
1380
            &[("ZvalConvert", zval_convert_derive_internal)],
1381
        )
1382
        .expect("Failed to expand derive macros in test file");
1383
    }
1384
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc