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

davidcole1340 / ext-php-rs / 16401140698

20 Jul 2025 02:13PM CUT coverage: 26.945% (+1.2%) from 25.766%
16401140698

Pull #489

github

Xenira
refactor(enum): use raii box pattern for enum values

Refs: #178
Pull Request #489: feat(enum): add basic enum support

118 of 288 new or added lines in 7 files covered. (40.97%)

1 existing line in 1 file now uncovered.

1115 of 4138 relevant lines covered (26.95%)

5.64 hits per line

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

12.31
/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 module;
11
mod parsing;
12
mod syn_ext;
13
mod zval;
14

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

19
extern crate proc_macro;
20

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

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

216
    class::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
217
}
218

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

336
fn php_enum_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
3✔
337
    let input = parse_macro_input2!(input as ItemEnum);
6✔
338

NEW
339
    enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
340
}
341

342
// BEGIN DOCS FROM function.md
343
/// # `#[php_function]` Attribute
344
///
345
/// Used to annotate functions which should be exported to PHP. Note that this
346
/// should not be used on class methods - see the `#[php_impl]` macro for that.
347
///
348
/// See the [list of types](../types/index.md) that are valid as parameter and
349
/// return types.
350
///
351
/// ## Optional parameters
352
///
353
/// Optional parameters can be used by setting the Rust parameter type to a
354
/// variant of `Option<T>`. The macro will then figure out which parameters are
355
/// optional by using the last consecutive arguments that are a variant of
356
/// `Option<T>` or have a default value.
357
///
358
/// ```rust,no_run,ignore
359
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
360
/// # extern crate ext_php_rs;
361
/// use ext_php_rs::prelude::*;
362
///
363
/// #[php_function]
364
/// pub fn greet(name: String, age: Option<i32>) -> String {
365
///     let mut greeting = format!("Hello, {}!", name);
366
///
367
///     if let Some(age) = age {
368
///         greeting += &format!(" You are {} years old.", age);
369
///     }
370
///
371
///     greeting
372
/// }
373
///
374
/// #[php_module]
375
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
376
///     module.function(wrap_function!(greet))
377
/// }
378
/// # fn main() {}
379
/// ```
380
///
381
/// Default parameter values can also be set for optional parameters. This is
382
/// done through the `#[php(defaults)]` attribute option. When an optional
383
/// parameter has a default, it does not need to be a variant of `Option`:
384
///
385
/// ```rust,no_run,ignore
386
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
387
/// # extern crate ext_php_rs;
388
/// use ext_php_rs::prelude::*;
389
///
390
/// #[php_function]
391
/// #[php(defaults(offset = 0))]
392
/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
393
///     let haystack: String = haystack.chars().skip(offset as usize).collect();
394
///     haystack.find(needle)
395
/// }
396
///
397
/// #[php_module]
398
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
399
///     module.function(wrap_function!(rusty_strpos))
400
/// }
401
/// # fn main() {}
402
/// ```
403
///
404
/// Note that if there is a non-optional argument after an argument that is a
405
/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
406
/// argument rather than an optional argument.
407
///
408
/// ```rust,no_run,ignore
409
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
410
/// # extern crate ext_php_rs;
411
/// use ext_php_rs::prelude::*;
412
///
413
/// /// `age` will be deemed required and nullable rather than optional.
414
/// #[php_function]
415
/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
416
///     let mut greeting = format!("Hello, {}!", name);
417
///
418
///     if let Some(age) = age {
419
///         greeting += &format!(" You are {} years old.", age);
420
///     }
421
///
422
///     greeting += &format!(" {}.", description);
423
///     greeting
424
/// }
425
///
426
/// #[php_module]
427
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
428
///     module.function(wrap_function!(greet))
429
/// }
430
/// # fn main() {}
431
/// ```
432
///
433
/// You can also specify the optional arguments if you want to have nullable
434
/// arguments before optional arguments. This is done through an attribute
435
/// parameter:
436
///
437
/// ```rust,no_run,ignore
438
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
439
/// # extern crate ext_php_rs;
440
/// use ext_php_rs::prelude::*;
441
///
442
/// /// `age` will be deemed required and nullable rather than optional,
443
/// /// while description will be optional.
444
/// #[php_function]
445
/// #[php(optional = "description")]
446
/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
447
///     let mut greeting = format!("Hello, {}!", name);
448
///
449
///     if let Some(age) = age {
450
///         greeting += &format!(" You are {} years old.", age);
451
///     }
452
///
453
///     if let Some(description) = description {
454
///         greeting += &format!(" {}.", description);
455
///     }
456
///
457
///     greeting
458
/// }
459
///
460
/// #[php_module]
461
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
462
///     module.function(wrap_function!(greet))
463
/// }
464
/// # fn main() {}
465
/// ```
466
///
467
/// ## Variadic Functions
468
///
469
/// Variadic functions can be implemented by specifying the last argument in the
470
/// Rust function to the type `&[&Zval]`. This is the equivalent of a PHP
471
/// function using the `...$args` syntax.
472
///
473
/// ```rust,no_run,ignore
474
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
475
/// # extern crate ext_php_rs;
476
/// use ext_php_rs::{prelude::*, types::Zval};
477
///
478
/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
479
/// #[php_function]
480
/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
481
///     // numbers is a slice of 4 Zvals all of type long
482
///     number
483
/// }
484
///
485
/// #[php_module]
486
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
487
///     module.function(wrap_function!(add))
488
/// }
489
/// # fn main() {}
490
/// ```
491
///
492
/// ## Returning `Result<T, E>`
493
///
494
/// You can also return a `Result` from the function. The error variant will be
495
/// translated into an exception and thrown. See the section on
496
/// [exceptions](../exceptions.md) for more details.
497
// END DOCS FROM function.md
498
#[proc_macro_attribute]
499
pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
×
500
    php_function_internal(args.into(), input.into()).into()
×
501
}
502

503
#[allow(clippy::needless_pass_by_value)]
504
fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
505
    let input = parse_macro_input2!(input as ItemFn);
×
506
    if !args.is_empty() {
507
        return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
508
    }
509

510
    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
511
}
512

513
// BEGIN DOCS FROM constant.md
514
/// # `#[php_const]` Attribute
515
///
516
/// Exports a Rust constant as a global PHP constant. The constant can be any
517
/// type that implements `IntoConst`.
518
///
519
/// The `wrap_constant!()` macro can be used to simplify the registration of
520
/// constants. It sets the name and doc comments for the constant.
521
///
522
/// You can rename the const with options:
523
///
524
/// - `name` - Allows you to rename the property, e.g. `#[php(name =
525
///   "new_name")]`
526
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
527
///   `#[php(change_case = PascalCase)]`
528
///
529
/// ## Examples
530
///
531
/// ```rust,no_run,ignore
532
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
533
/// # extern crate ext_php_rs;
534
/// use ext_php_rs::prelude::*;
535
///
536
/// #[php_const]
537
/// const TEST_CONSTANT: i32 = 100;
538
///
539
/// #[php_const]
540
/// #[php(name = "I_AM_RENAMED")]
541
/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
542
///
543
/// #[php_const]
544
/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
545
///
546
/// #[php_module]
547
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
548
///     module
549
///         .constant(wrap_constant!(TEST_CONSTANT))
550
///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
551
///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
552
/// }
553
/// # fn main() {}
554
/// ```
555
///
556
/// ## PHP usage
557
///
558
/// ```php
559
/// <?php
560
///
561
/// var_dump(TEST_CONSTANT); // int(100)
562
/// var_dump(I_AM_RENAMED); // int(42)
563
/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
564
/// ```
565
// END DOCS FROM constant.md
566
#[proc_macro_attribute]
567
pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
×
568
    php_const_internal(args.into(), input.into()).into()
×
569
}
570

571
#[allow(clippy::needless_pass_by_value)]
572
fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1✔
573
    let input = parse_macro_input2!(input as ItemConst);
2✔
574
    if !args.is_empty() {
575
        return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
576
    }
577

578
    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
3✔
579
}
580

581
// BEGIN DOCS FROM module.md
582
/// # `#[php_module]` Attribute
583
///
584
/// The module macro is used to annotate the `get_module` function, which is
585
/// used by the PHP interpreter to retrieve information about your extension,
586
/// including the name, version, functions and extra initialization functions.
587
/// Regardless if you use this macro, your extension requires a `extern "C" fn
588
/// get_module()` so that PHP can get this information.
589
///
590
/// The function is renamed to `get_module` if you have used another name. The
591
/// function is passed an instance of `ModuleBuilder` which allows you to
592
/// register the following (if required):
593
///
594
/// - Functions, classes, and constants
595
/// - Extension and request startup and shutdown functions.
596
///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
597
/// - PHP extension information function
598
///   - Used by the `phpinfo()` function to get information about your
599
///     extension.
600
///
601
/// Classes and constants are not registered with PHP in the `get_module`
602
/// function. These are registered inside the extension startup function.
603
///
604
/// ## Usage
605
///
606
/// ```rust,no_run,ignore
607
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
608
/// # extern crate ext_php_rs;
609
/// use ext_php_rs::{
610
///     prelude::*,
611
///     zend::ModuleEntry,
612
///     info_table_start,
613
///     info_table_row,
614
///     info_table_end
615
/// };
616
///
617
/// #[php_const]
618
/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
619
///
620
/// #[php_class]
621
/// pub struct Test {
622
///     a: i32,
623
///     b: i32
624
/// }
625
/// #[php_function]
626
/// pub fn hello_world() -> &'static str {
627
///     "Hello, world!"
628
/// }
629
///
630
/// /// Used by the `phpinfo()` function and when you run `php -i`.
631
/// /// This will probably be simplified with another macro eventually!
632
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
633
///     info_table_start!();
634
///     info_table_row!("my extension", "enabled");
635
///     info_table_end!();
636
/// }
637
///
638
/// #[php_module]
639
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
640
///     module
641
///         .constant(wrap_constant!(MY_CUSTOM_CONST))
642
///         .class::<Test>()
643
///         .function(wrap_function!(hello_world))
644
///         .info_function(php_module_info)
645
/// }
646
/// # fn main() {}
647
/// ```
648
// END DOCS FROM module.md
649
#[proc_macro_attribute]
650
pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
×
651
    php_module_internal(args.into(), input.into()).into()
×
652
}
653

654
#[allow(clippy::needless_pass_by_value)]
655
fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
656
    let input = parse_macro_input2!(input as ItemFn);
×
657
    if !args.is_empty() {
658
        return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
659
    }
660

661
    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
662
}
663

664
// BEGIN DOCS FROM impl.md
665
/// # `#[php_impl]` Attribute
666
///
667
/// You can export an entire `impl` block to PHP. This exports all methods as
668
/// well as constants to PHP on the class that it is implemented on. This
669
/// requires the `#[php_class]` macro to already be used on the underlying
670
/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
671
/// block can be exported per class.
672
///
673
/// If you do not want a function exported to PHP, you should place it in a
674
/// separate `impl` block.
675
///
676
/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
677
/// &raquo;](./async_impl.md) for more info.
678
///
679
/// ## Options
680
///
681
/// By default all constants are renamed to `UPPER_CASE` and all methods are
682
/// renamed to camelCase. This can be changed by passing the
683
/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
684
/// the `impl` block. The options are:
685
///
686
/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
687
///   case.
688
/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
689
///   snake case.
690
///
691
/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
692
/// for a list of all available cases.
693
///
694
/// ## Methods
695
///
696
/// Methods basically follow the same rules as functions, so read about the
697
/// [`php_function`] macro first. The primary difference between functions and
698
/// methods is they are bounded by their class object.
699
///
700
/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
701
/// a consuming `self` parameter. Static methods can omit this `self` parameter.
702
///
703
/// To access the underlying Zend object, you can take a reference to a
704
/// `ZendClassObject<T>` in place of the self parameter, where the parameter
705
/// must be named `self_`. This can also be used to return a reference to
706
/// `$this`.
707
///
708
/// The rest of the options are passed as separate attributes:
709
///
710
/// - `#[php(defaults(i = 5, b = "hello"))]` - Sets the default value for
711
///   parameter(s).
712
/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
713
///   also sets the remaining parameters as optional, so all optional parameters
714
///   must be a variant of `Option<T>`.
715
/// - `#[php(public)]`, `#[php(protected)]` and `#[php(private)]` - Sets the
716
///   visibility of the method.
717
/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
718
///   identifier, without renaming the Rust method name.
719
///
720
/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
721
/// the equivalent function attribute parameters.
722
///
723
/// ### Constructors
724
///
725
/// By default, if a class does not have a constructor, it is not constructable
726
/// from PHP. It can only be returned from a Rust function to PHP.
727
///
728
/// Constructors are Rust methods which can take any amount of parameters and
729
/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
730
/// When the error variant of `Result` is encountered, it is thrown as an
731
/// exception and the class is not constructed.
732
///
733
/// Constructors are designated by either naming the method `__construct` or by
734
/// annotating a method with the `#[php(constructor)]` attribute. Note that when
735
/// using the attribute, the function is not exported to PHP like a regular
736
/// method.
737
///
738
/// Constructors cannot use the visibility or rename attributes listed above.
739
///
740
/// ## Constants
741
///
742
/// Constants are defined as regular Rust `impl` constants. Any type that
743
/// implements `IntoZval` can be used as a constant. Constant visibility is not
744
/// supported at the moment, and therefore no attributes are valid on constants.
745
///
746
/// ## Property getters and setters
747
///
748
/// You can add properties to classes which use Rust functions as getters and/or
749
/// setters. This is done with the `#[php(getter)]` and `#[php(setter)]`
750
/// attributes. By default, the `get_` or `set_` prefix is trimmed from the
751
/// start of the function name, and the remainder is used as the property name.
752
///
753
/// If you want to use a different name for the property, you can pass a `name`
754
/// or `change_case` option to the `#[php]` attribute which will change the
755
/// property name.
756
///
757
/// Properties do not necessarily have to have both a getter and a setter, if
758
/// the property is immutable the setter can be omitted, and vice versa for
759
/// getters.
760
///
761
/// The `#[php(getter)]` and `#[php(setter)]` attributes are mutually exclusive
762
/// on methods. Properties cannot have multiple getters or setters, and the
763
/// property name cannot conflict with field properties defined on the struct.
764
///
765
/// As the same as field properties, method property types must implement both
766
/// `IntoZval` and `FromZval`.
767
///
768
/// ## Example
769
///
770
/// Continuing on from our `Human` example in the structs section, we will
771
/// define a constructor, as well as getters for the properties. We will also
772
/// define a constant for the maximum age of a `Human`.
773
///
774
/// ```rust,no_run,ignore
775
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
776
/// # extern crate ext_php_rs;
777
/// use ext_php_rs::{prelude::*, types::ZendClassObject};
778
///
779
/// #[php_class]
780
/// #[derive(Debug, Default)]
781
/// pub struct Human {
782
///     name: String,
783
///     age: i32,
784
///     #[php(prop)]
785
///     address: String,
786
/// }
787
///
788
/// #[php_impl]
789
/// impl Human {
790
///     const MAX_AGE: i32 = 100;
791
///
792
///     // No `#[constructor]` attribute required here - the name is `__construct`.
793
///     pub fn __construct(name: String, age: i32) -> Self {
794
///         Self {
795
///             name,
796
///             age,
797
///             address: String::new()
798
///         }
799
///     }
800
///
801
///     #[php(getter)]
802
///     pub fn get_name(&self) -> String {
803
///         self.name.to_string()
804
///     }
805
///
806
///     #[php(setter)]
807
///     pub fn set_name(&mut self, name: String) {
808
///         self.name = name;
809
///     }
810
///
811
///     #[php(getter)]
812
///     pub fn get_age(&self) -> i32 {
813
///         self.age
814
///     }
815
///
816
///     pub fn introduce(&self) {
817
///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
818
///     }
819
///
820
///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
821
///         dbg!(self_)
822
///     }
823
///
824
///     pub fn get_max_age() -> i32 {
825
///         Self::MAX_AGE
826
///     }
827
/// }
828
/// #[php_module]
829
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
830
///     module.class::<Human>()
831
/// }
832
/// # fn main() {}
833
/// ```
834
///
835
/// Using our newly created class in PHP:
836
///
837
/// ```php
838
/// <?php
839
///
840
/// $me = new Human('David', 20);
841
///
842
/// $me->introduce(); // My name is David and I am 20 years old.
843
/// var_dump(Human::get_max_age()); // int(100)
844
/// var_dump(Human::MAX_AGE); // int(100)
845
/// ```
846
///
847
/// [`php_async_impl`]: ./async_impl.md
848
// END DOCS FROM impl.md
849
#[proc_macro_attribute]
850
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
×
851
    php_impl_internal(args.into(), input.into()).into()
×
852
}
853

854
#[allow(clippy::needless_pass_by_value)]
855
fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
856
    let input = parse_macro_input2!(input as ItemImpl);
×
857
    if !args.is_empty() {
858
        return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
859
    }
860

861
    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
862
}
863

864
// BEGIN DOCS FROM extern.md
865
/// # `#[php_extern]` Attribute
866
///
867
/// Attribute used to annotate `extern` blocks which are deemed as PHP
868
/// functions.
869
///
870
/// This allows you to 'import' PHP functions into Rust so that they can be
871
/// called like regular Rust functions. Parameters can be any type that
872
/// implements [`IntoZval`], and the return type can be anything that implements
873
/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
874
/// case).
875
///
876
/// Unlike most other attributes, this does not need to be placed inside a
877
/// `#[php_module]` block.
878
///
879
/// # Panics
880
///
881
/// The function can panic when called under a few circumstances:
882
///
883
/// * The function could not be found or was not callable.
884
/// * One of the parameters could not be converted into a [`Zval`].
885
/// * The actual function call failed internally.
886
/// * The output [`Zval`] could not be parsed into the output type.
887
///
888
/// The last point can be important when interacting with functions that return
889
/// unions, such as [`strpos`] which can return an integer or a boolean. In this
890
/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
891
/// invalid, and vice versa.
892
///
893
/// # Example
894
///
895
/// This `extern` block imports the [`strpos`] function from PHP. Notice that
896
/// the string parameters can take either [`String`] or [`&str`], the optional
897
/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
898
/// as the return type is an integer-boolean union.
899
///
900
/// ```rust,no_run,ignore
901
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
902
/// # extern crate ext_php_rs;
903
/// use ext_php_rs::{
904
///     prelude::*,
905
///     types::Zval,
906
/// };
907
///
908
/// #[php_extern]
909
/// extern "C" {
910
///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
911
/// }
912
///
913
/// #[php_function]
914
/// pub fn my_strpos() {
915
///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
916
/// }
917
///
918
/// #[php_module]
919
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
920
///     module.function(wrap_function!(my_strpos))
921
/// }
922
/// # fn main() {}
923
/// ```
924
///
925
/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
926
/// [`IntoZval`]: crate::convert::IntoZval
927
/// [`Zval`]: crate::types::Zval
928
// END DOCS FROM extern.md
929
#[proc_macro_attribute]
930
pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
×
931
    php_extern_internal(args.into(), input.into()).into()
×
932
}
933

934
#[allow(clippy::needless_pass_by_value)]
935
fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
936
    let input = parse_macro_input2!(input as ItemForeignMod);
×
937

938
    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
939
}
940

941
// BEGIN DOCS FROM zval_convert.md
942
/// # `ZvalConvert` Derive Macro
943
///
944
/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
945
/// traits on a struct or enum.
946
///
947
/// ## Structs
948
///
949
/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
950
/// also implemented, mapping fields to properties in both directions. All
951
/// fields on the struct must implement `FromZval` as well. Generics are allowed
952
/// on structs that use the derive macro, however, the implementation will add a
953
/// `FromZval` bound to all generics types.
954
///
955
/// ### Examples
956
///
957
/// ```rust,no_run,ignore
958
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
959
/// # extern crate ext_php_rs;
960
/// use ext_php_rs::prelude::*;
961
///
962
/// #[derive(ZvalConvert)]
963
/// pub struct ExampleClass<'a> {
964
///     a: i32,
965
///     b: String,
966
///     c: &'a str
967
/// }
968
///
969
/// #[php_function]
970
/// pub fn take_object(obj: ExampleClass) {
971
///     dbg!(obj.a, obj.b, obj.c);
972
/// }
973
///
974
/// #[php_function]
975
/// pub fn give_object() -> ExampleClass<'static> {
976
///     ExampleClass {
977
///         a: 5,
978
///         b: "String".to_string(),
979
///         c: "Borrowed",
980
///     }
981
/// }
982
///
983
/// #[php_module]
984
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
985
///     module
986
///         .function(wrap_function!(take_object))
987
///         .function(wrap_function!(give_object))
988
/// }
989
/// # fn main() {}
990
/// ```
991
///
992
/// Calling from PHP:
993
///
994
/// ```php
995
/// <?php
996
///
997
/// $obj = new stdClass;
998
/// $obj->a = 5;
999
/// $obj->b = 'Hello, world!';
1000
/// $obj->c = 'another string';
1001
///
1002
/// take_object($obj);
1003
/// var_dump(give_object());
1004
/// ```
1005
///
1006
/// Another example involving generics:
1007
///
1008
/// ```rust,no_run,ignore
1009
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1010
/// # extern crate ext_php_rs;
1011
/// use ext_php_rs::prelude::*;
1012
///
1013
/// // T must implement both `PartialEq<i32>` and `FromZval`.
1014
/// #[derive(Debug, ZvalConvert)]
1015
/// pub struct CompareVals<T: PartialEq<i32>> {
1016
///     a: T,
1017
///     b: T
1018
/// }
1019
///
1020
/// #[php_function]
1021
/// pub fn take_object(obj: CompareVals<i32>) {
1022
///     dbg!(obj);
1023
/// }
1024
///
1025
/// #[php_module]
1026
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1027
///     module
1028
///         .function(wrap_function!(take_object))
1029
/// }
1030
/// # fn main() {}
1031
/// ```
1032
///
1033
/// ## Enums
1034
///
1035
/// When used on an enum, the `FromZval` implementation will treat the enum as a
1036
/// tagged union with a mixed datatype. This allows you to accept multiple types
1037
/// in a parameter, for example, a string and an integer.
1038
///
1039
/// The enum variants must not have named fields, and each variant must have
1040
/// exactly one field (the type to extract from the zval). Optionally, the enum
1041
/// may have one default variant with no data contained, which will be used when
1042
/// the rest of the variants could not be extracted from the zval.
1043
///
1044
/// The ordering of the variants in the enum is important, as the `FromZval`
1045
/// implementation will attempt to parse the zval data in order. For example, if
1046
/// you put a `String` variant before an integer variant, the integer would be
1047
/// converted to a string and passed as the string variant.
1048
///
1049
/// ### Examples
1050
///
1051
/// Basic example showing the importance of variant ordering and default field:
1052
///
1053
/// ```rust,no_run,ignore
1054
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1055
/// # extern crate ext_php_rs;
1056
/// use ext_php_rs::prelude::*;
1057
///
1058
/// #[derive(Debug, ZvalConvert)]
1059
/// pub enum UnionExample<'a> {
1060
///     Long(u64), // Long
1061
///     ProperStr(&'a str), // Actual string - not a converted value
1062
///     ParsedStr(String), // Potentially parsed string, i.e. a double
1063
///     None // Zval did not contain anything that could be parsed above
1064
/// }
1065
///
1066
/// #[php_function]
1067
/// pub fn test_union(val: UnionExample) {
1068
///     dbg!(val);
1069
/// }
1070
///
1071
/// #[php_function]
1072
/// pub fn give_union() -> UnionExample<'static> {
1073
///     UnionExample::Long(5)
1074
/// }
1075
///
1076
/// #[php_module]
1077
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1078
///     module
1079
///         .function(wrap_function!(test_union))
1080
///         .function(wrap_function!(give_union))
1081
/// }
1082
/// # fn main() {}
1083
/// ```
1084
///
1085
/// Use in PHP:
1086
///
1087
/// ```php
1088
/// test_union(5); // UnionExample::Long(5)
1089
/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
1090
/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
1091
/// test_union(null); // UnionExample::None
1092
/// var_dump(give_union()); // int(5)
1093
/// ```
1094
// END DOCS FROM zval_convert.md
1095
#[proc_macro_derive(ZvalConvert)]
1096
pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
×
1097
    zval_convert_derive_internal(input.into()).into()
×
1098
}
1099

1100
fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
×
1101
    let input = parse_macro_input2!(input as DeriveInput);
×
1102

1103
    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
1104
}
1105

1106
/// Defines an `extern` function with the Zend fastcall convention based on
1107
/// operating system.
1108
///
1109
/// On Windows, Zend fastcall functions use the vector calling convention, while
1110
/// on all other operating systems no fastcall convention is used (just the
1111
/// regular C calling convention).
1112
///
1113
/// This macro wraps a function and applies the correct calling convention.
1114
///
1115
/// ## Examples
1116
///
1117
/// ```rust,ignore
1118
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1119
/// use ext_php_rs::zend_fastcall;
1120
///
1121
/// zend_fastcall! {
1122
///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
1123
///         a + b
1124
///     }
1125
/// }
1126
/// ```
1127
///
1128
/// On Windows, this function will have the signature `pub extern "vectorcall"
1129
/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
1130
/// signature `pub extern "C" fn(i32, i32) -> i32`.
1131
///
1132
/// ## Support
1133
///
1134
/// The `vectorcall` ABI is currently only supported on Windows with nightly
1135
/// Rust and the `abi_vectorcall` feature enabled.
1136
#[proc_macro]
1137
pub fn zend_fastcall(input: TokenStream) -> TokenStream {
×
1138
    zend_fastcall_internal(input.into()).into()
×
1139
}
1140

1141
fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
×
1142
    let input = parse_macro_input2!(input as ItemFn);
×
1143

1144
    fastcall::parser(input)
1145
}
1146

1147
/// Wraps a function to be used in the [`Module::function`] method.
1148
#[proc_macro]
1149
pub fn wrap_function(input: TokenStream) -> TokenStream {
×
1150
    wrap_function_internal(input.into()).into()
×
1151
}
1152

1153
fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
×
1154
    let input = parse_macro_input2!(input as syn::Path);
×
1155

1156
    match function::wrap(&input) {
1157
        Ok(parsed) => parsed,
×
1158
        Err(e) => e.to_compile_error(),
×
1159
    }
1160
}
1161

1162
/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
1163
#[proc_macro]
1164
pub fn wrap_constant(input: TokenStream) -> TokenStream {
×
1165
    wrap_constant_internal(input.into()).into()
×
1166
}
1167

1168
fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1✔
1169
    let input = parse_macro_input2!(input as syn::Path);
2✔
1170

1171
    match constant::wrap(&input) {
1172
        Ok(parsed) => parsed,
1✔
1173
        Err(e) => e.to_compile_error(),
×
1174
    }
1175
}
1176

1177
macro_rules! parse_macro_input2 {
1178
    ($tokenstream:ident as $ty:ty) => {
1179
        match syn::parse2::<$ty>($tokenstream) {
1180
            Ok(data) => data,
1181
            Err(err) => {
1182
                return proc_macro2::TokenStream::from(err.to_compile_error());
1183
            }
1184
        }
1185
    };
1186
    ($tokenstream:ident) => {
1187
        $crate::parse_macro_input!($tokenstream as _)
1188
    };
1189
}
1190

1191
pub(crate) use parse_macro_input2;
1192

1193
macro_rules! err {
1194
    ($span:expr => $($msg:tt)*) => {
1195
        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
1196
    };
1197
    ($($msg:tt)*) => {
1198
        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
1199
    };
1200
}
1201

1202
/// Bails out of a function with a syn error.
1203
macro_rules! bail {
1204
    ($span:expr => $($msg:tt)*) => {
1205
        return Err($crate::err!($span => $($msg)*))
1206
    };
1207
    ($($msg:tt)*) => {
1208
        return Err($crate::err!($($msg)*))
1209
    };
1210
}
1211

1212
pub(crate) use bail;
1213
pub(crate) use err;
1214

1215
pub(crate) mod prelude {
1216
    pub(crate) trait OptionTokens {
1217
        fn option_tokens(&self) -> proc_macro2::TokenStream;
1218
    }
1219

1220
    impl<T: quote::ToTokens> OptionTokens for Option<T> {
1221
        fn option_tokens(&self) -> proc_macro2::TokenStream {
×
1222
            if let Some(token) = self {
×
1223
                quote::quote! { ::std::option::Option::Some(#token) }
×
1224
            } else {
1225
                quote::quote! { ::std::option::Option::None }
×
1226
            }
1227
        }
1228
    }
1229

1230
    pub(crate) use crate::{bail, err};
1231
    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
1232
}
1233

1234
#[cfg(test)]
1235
mod tests {
1236
    use super::*;
1237
    use std::path::PathBuf;
1238

1239
    type AttributeFn =
1240
        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1241
    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1242

1243
    #[test]
1244
    pub fn test_expand() {
1245
        macrotest::expand("tests/expand/*.rs");
1246
        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1247
            let entry = entry.expect("Failed to read expand test file");
1248
            runtime_expand_attr(&entry);
1249
            runtime_expand_func(&entry);
1250
            runtime_expand_derive(&entry);
1251
        }
1252
    }
1253

1254
    fn runtime_expand_attr(path: &PathBuf) {
1255
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1256
        runtime_macros::emulate_attributelike_macro_expansion(
1257
            file,
1258
            &[
1259
                ("php_class", php_class_internal as AttributeFn),
1260
                ("php_const", php_const_internal as AttributeFn),
1261
                ("php_enum", php_enum_internal as AttributeFn),
1262
                ("php_extern", php_extern_internal as AttributeFn),
1263
                ("php_function", php_function_internal as AttributeFn),
1264
                ("php_impl", php_impl_internal as AttributeFn),
1265
                ("php_module", php_module_internal as AttributeFn),
1266
            ],
1267
        )
1268
        .expect("Failed to expand attribute macros in test file");
1269
    }
1270

1271
    fn runtime_expand_func(path: &PathBuf) {
1272
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1273
        runtime_macros::emulate_functionlike_macro_expansion(
1274
            file,
1275
            &[
1276
                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1277
                ("wrap_function", wrap_function_internal as FunctionLikeFn),
1278
                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1279
            ],
1280
        )
1281
        .expect("Failed to expand function-like macros in test file");
1282
    }
1283

1284
    fn runtime_expand_derive(path: &PathBuf) {
1285
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1286
        runtime_macros::emulate_derive_macro_expansion(
1287
            file,
1288
            &[("ZvalConvert", zval_convert_derive_internal)],
1289
        )
1290
        .expect("Failed to expand derive macros in test file");
1291
    }
1292
}
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