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

davidcole1340 / ext-php-rs / 16880353614

11 Aug 2025 12:42PM UTC coverage: 26.825% (-1.0%) from 27.778%
16880353614

Pull #533

github

web-flow
Merge 9e9410bbe into 896cb945d
Pull Request #533: Feat/interface impl

8 of 179 new or added lines in 9 files covered. (4.47%)

2 existing lines in 2 files now uncovered.

1161 of 4328 relevant lines covered (26.83%)

7.42 hits per line

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

11.43
/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 {
×
214
    let input = parse_macro_input2!(input as ItemStruct);
×
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())
×
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
/// Traits can be exported to PHP as interface with the `#[php_interface]` attribute
349
/// macro. This attribute generate empty struct and derives the `RegisteredClass`.
350
/// To register the interface use the `interface::<PhpInterface{TraitName}>()` method
351
/// on the `ModuleBuilder` in the `#[php_module]` macro.
352
///
353
/// ## Options
354
///
355
/// The `#[php_interface]` attribute can be configured with the following options:
356
/// - `#[php(name = "InterfaceName")]` or `#[php(change_case = snake_case)]`: Sets
357
///   the name of the interface in PHP. The default is the `PascalCase` name of the
358
///   interface.
359
/// - `#[php(extends(ce = ce::throwable, stub = "\\Throwable"))]`
360
///   to extends interface from other interface
361
///
362
/// ### Example
363
///
364
/// This example creates a PHP interface extend from php buildin Throwable.
365
///
366
/// ```rust,no_run,ignore
367
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
368
/// # extern crate ext_php_rs;
369
/// use ext_php_rs::prelude::*;
370
///
371
/// #[php_interface]
372
/// #[php(extends(ce = ce::throwable, stub = "\\Throwable"))]
373
/// #[php(name = "LibName\\Exception\\MyCustomDomainException")]
374
/// pub trait MyCustomDomainException {
375
///     fn createWithMessage(message: String) -> Self;
376
/// }
377
///
378
/// #[php_module]
379
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
380
///     module.interface::<PhpInterfaceMyCustomDomainException>()
381
/// }
382
/// # fn main() {}
383
/// ```
384
// END DOCS FROM interface.md
385
#[proc_macro_attribute]
NEW
386
pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
×
NEW
387
    php_interface_internal(args.into(), input.into()).into()
×
388
}
389

NEW
390
fn php_interface_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
NEW
391
    let input = parse_macro_input2!(input as ItemTrait);
×
392

NEW
393
    interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
394
}
395

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

557
#[allow(clippy::needless_pass_by_value)]
558
fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
559
    let input = parse_macro_input2!(input as ItemFn);
×
560
    if !args.is_empty() {
561
        return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
562
    }
563

564
    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
565
}
566

567
// BEGIN DOCS FROM constant.md
568
/// # `#[php_const]` Attribute
569
///
570
/// Exports a Rust constant as a global PHP constant. The constant can be any
571
/// type that implements `IntoConst`.
572
///
573
/// The `wrap_constant!()` macro can be used to simplify the registration of
574
/// constants. It sets the name and doc comments for the constant.
575
///
576
/// You can rename the const with options:
577
///
578
/// - `name` - Allows you to rename the property, e.g. `#[php(name =
579
///   "new_name")]`
580
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
581
///   `#[php(change_case = PascalCase)]`
582
///
583
/// ## Examples
584
///
585
/// ```rust,no_run,ignore
586
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
587
/// # extern crate ext_php_rs;
588
/// use ext_php_rs::prelude::*;
589
///
590
/// #[php_const]
591
/// const TEST_CONSTANT: i32 = 100;
592
///
593
/// #[php_const]
594
/// #[php(name = "I_AM_RENAMED")]
595
/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
596
///
597
/// #[php_const]
598
/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
599
///
600
/// #[php_module]
601
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
602
///     module
603
///         .constant(wrap_constant!(TEST_CONSTANT))
604
///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
605
///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
606
/// }
607
/// # fn main() {}
608
/// ```
609
///
610
/// ## PHP usage
611
///
612
/// ```php
613
/// <?php
614
///
615
/// var_dump(TEST_CONSTANT); // int(100)
616
/// var_dump(I_AM_RENAMED); // int(42)
617
/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
618
/// ```
619
// END DOCS FROM constant.md
620
#[proc_macro_attribute]
621
pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
×
622
    php_const_internal(args.into(), input.into()).into()
×
623
}
624

625
#[allow(clippy::needless_pass_by_value)]
626
fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1✔
627
    let input = parse_macro_input2!(input as ItemConst);
2✔
628
    if !args.is_empty() {
629
        return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
630
    }
631

632
    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
3✔
633
}
634

635
// BEGIN DOCS FROM module.md
636
/// # `#[php_module]` Attribute
637
///
638
/// The module macro is used to annotate the `get_module` function, which is
639
/// used by the PHP interpreter to retrieve information about your extension,
640
/// including the name, version, functions and extra initialization functions.
641
/// Regardless if you use this macro, your extension requires a `extern "C" fn
642
/// get_module()` so that PHP can get this information.
643
///
644
/// The function is renamed to `get_module` if you have used another name. The
645
/// function is passed an instance of `ModuleBuilder` which allows you to
646
/// register the following (if required):
647
///
648
/// - Functions, classes, and constants
649
/// - Extension and request startup and shutdown functions.
650
///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
651
/// - PHP extension information function
652
///   - Used by the `phpinfo()` function to get information about your
653
///     extension.
654
///
655
/// Classes and constants are not registered with PHP in the `get_module`
656
/// function. These are registered inside the extension startup function.
657
///
658
/// ## Usage
659
///
660
/// ```rust,no_run,ignore
661
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
662
/// # extern crate ext_php_rs;
663
/// use ext_php_rs::{
664
///     prelude::*,
665
///     zend::ModuleEntry,
666
///     info_table_start,
667
///     info_table_row,
668
///     info_table_end
669
/// };
670
///
671
/// #[php_const]
672
/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
673
///
674
/// #[php_class]
675
/// pub struct Test {
676
///     a: i32,
677
///     b: i32
678
/// }
679
/// #[php_function]
680
/// pub fn hello_world() -> &'static str {
681
///     "Hello, world!"
682
/// }
683
///
684
/// /// Used by the `phpinfo()` function and when you run `php -i`.
685
/// /// This will probably be simplified with another macro eventually!
686
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
687
///     info_table_start!();
688
///     info_table_row!("my extension", "enabled");
689
///     info_table_end!();
690
/// }
691
///
692
/// #[php_module]
693
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
694
///     module
695
///         .constant(wrap_constant!(MY_CUSTOM_CONST))
696
///         .class::<Test>()
697
///         .function(wrap_function!(hello_world))
698
///         .info_function(php_module_info)
699
/// }
700
/// # fn main() {}
701
/// ```
702
// END DOCS FROM module.md
703
#[proc_macro_attribute]
704
pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
×
705
    php_module_internal(args.into(), input.into()).into()
×
706
}
707

708
#[allow(clippy::needless_pass_by_value)]
709
fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
710
    let input = parse_macro_input2!(input as ItemFn);
×
711
    if !args.is_empty() {
712
        return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
713
    }
714

715
    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
716
}
717

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

908
#[allow(clippy::needless_pass_by_value)]
909
fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
910
    let input = parse_macro_input2!(input as ItemImpl);
×
911
    if !args.is_empty() {
912
        return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
×
913
    }
914

915
    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
916
}
917

918
// BEGIN DOCS FROM extern.md
919
/// # `#[php_extern]` Attribute
920
///
921
/// Attribute used to annotate `extern` blocks which are deemed as PHP
922
/// functions.
923
///
924
/// This allows you to 'import' PHP functions into Rust so that they can be
925
/// called like regular Rust functions. Parameters can be any type that
926
/// implements [`IntoZval`], and the return type can be anything that implements
927
/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
928
/// case).
929
///
930
/// Unlike most other attributes, this does not need to be placed inside a
931
/// `#[php_module]` block.
932
///
933
/// # Panics
934
///
935
/// The function can panic when called under a few circumstances:
936
///
937
/// * The function could not be found or was not callable.
938
/// * One of the parameters could not be converted into a [`Zval`].
939
/// * The actual function call failed internally.
940
/// * The output [`Zval`] could not be parsed into the output type.
941
///
942
/// The last point can be important when interacting with functions that return
943
/// unions, such as [`strpos`] which can return an integer or a boolean. In this
944
/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
945
/// invalid, and vice versa.
946
///
947
/// # Example
948
///
949
/// This `extern` block imports the [`strpos`] function from PHP. Notice that
950
/// the string parameters can take either [`String`] or [`&str`], the optional
951
/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
952
/// as the return type is an integer-boolean union.
953
///
954
/// ```rust,no_run,ignore
955
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
956
/// # extern crate ext_php_rs;
957
/// use ext_php_rs::{
958
///     prelude::*,
959
///     types::Zval,
960
/// };
961
///
962
/// #[php_extern]
963
/// extern "C" {
964
///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
965
/// }
966
///
967
/// #[php_function]
968
/// pub fn my_strpos() {
969
///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
970
/// }
971
///
972
/// #[php_module]
973
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
974
///     module.function(wrap_function!(my_strpos))
975
/// }
976
/// # fn main() {}
977
/// ```
978
///
979
/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
980
/// [`IntoZval`]: crate::convert::IntoZval
981
/// [`Zval`]: crate::types::Zval
982
// END DOCS FROM extern.md
983
#[proc_macro_attribute]
984
pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
×
985
    php_extern_internal(args.into(), input.into()).into()
×
986
}
987

988
#[allow(clippy::needless_pass_by_value)]
989
fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
×
990
    let input = parse_macro_input2!(input as ItemForeignMod);
×
991

992
    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
993
}
994

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

1154
fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
×
1155
    let input = parse_macro_input2!(input as DeriveInput);
×
1156

1157
    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
×
1158
}
1159

1160
/// Defines an `extern` function with the Zend fastcall convention based on
1161
/// operating system.
1162
///
1163
/// On Windows, Zend fastcall functions use the vector calling convention, while
1164
/// on all other operating systems no fastcall convention is used (just the
1165
/// regular C calling convention).
1166
///
1167
/// This macro wraps a function and applies the correct calling convention.
1168
///
1169
/// ## Examples
1170
///
1171
/// ```rust,ignore
1172
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1173
/// use ext_php_rs::zend_fastcall;
1174
///
1175
/// zend_fastcall! {
1176
///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
1177
///         a + b
1178
///     }
1179
/// }
1180
/// ```
1181
///
1182
/// On Windows, this function will have the signature `pub extern "vectorcall"
1183
/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
1184
/// signature `pub extern "C" fn(i32, i32) -> i32`.
1185
///
1186
/// ## Support
1187
///
1188
/// The `vectorcall` ABI is currently only supported on Windows with nightly
1189
/// Rust and the `abi_vectorcall` feature enabled.
1190
#[proc_macro]
1191
pub fn zend_fastcall(input: TokenStream) -> TokenStream {
×
1192
    zend_fastcall_internal(input.into()).into()
×
1193
}
1194

1195
fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
×
1196
    let input = parse_macro_input2!(input as ItemFn);
×
1197

1198
    fastcall::parser(input)
1199
}
1200

1201
/// Wraps a function to be used in the [`Module::function`] method.
1202
#[proc_macro]
1203
pub fn wrap_function(input: TokenStream) -> TokenStream {
×
1204
    wrap_function_internal(input.into()).into()
×
1205
}
1206

1207
fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
×
1208
    let input = parse_macro_input2!(input as syn::Path);
×
1209

1210
    match function::wrap(&input) {
1211
        Ok(parsed) => parsed,
×
1212
        Err(e) => e.to_compile_error(),
×
1213
    }
1214
}
1215

1216
/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
1217
#[proc_macro]
1218
pub fn wrap_constant(input: TokenStream) -> TokenStream {
×
1219
    wrap_constant_internal(input.into()).into()
×
1220
}
1221

1222
fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1✔
1223
    let input = parse_macro_input2!(input as syn::Path);
2✔
1224

1225
    match constant::wrap(&input) {
1226
        Ok(parsed) => parsed,
1✔
1227
        Err(e) => e.to_compile_error(),
×
1228
    }
1229
}
1230

1231
macro_rules! parse_macro_input2 {
1232
    ($tokenstream:ident as $ty:ty) => {
1233
        match syn::parse2::<$ty>($tokenstream) {
1234
            Ok(data) => data,
1235
            Err(err) => {
1236
                return proc_macro2::TokenStream::from(err.to_compile_error());
1237
            }
1238
        }
1239
    };
1240
    ($tokenstream:ident) => {
1241
        $crate::parse_macro_input!($tokenstream as _)
1242
    };
1243
}
1244

1245
pub(crate) use parse_macro_input2;
1246

1247
macro_rules! err {
1248
    ($span:expr => $($msg:tt)*) => {
1249
        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
1250
    };
1251
    ($($msg:tt)*) => {
1252
        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
1253
    };
1254
}
1255

1256
/// Bails out of a function with a syn error.
1257
macro_rules! bail {
1258
    ($span:expr => $($msg:tt)*) => {
1259
        return Err($crate::err!($span => $($msg)*))
1260
    };
1261
    ($($msg:tt)*) => {
1262
        return Err($crate::err!($($msg)*))
1263
    };
1264
}
1265

1266
pub(crate) use bail;
1267
pub(crate) use err;
1268

1269
pub(crate) mod prelude {
1270
    pub(crate) trait OptionTokens {
1271
        fn option_tokens(&self) -> proc_macro2::TokenStream;
1272
    }
1273

1274
    impl<T: quote::ToTokens> OptionTokens for Option<T> {
1275
        fn option_tokens(&self) -> proc_macro2::TokenStream {
×
1276
            if let Some(token) = self {
×
1277
                quote::quote! { ::std::option::Option::Some(#token) }
×
1278
            } else {
1279
                quote::quote! { ::std::option::Option::None }
×
1280
            }
1281
        }
1282
    }
1283

1284
    pub(crate) use crate::{bail, err};
1285
    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
1286
}
1287

1288
#[cfg(test)]
1289
mod tests {
1290
    use super::*;
1291
    use std::path::PathBuf;
1292

1293
    type AttributeFn =
1294
        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1295
    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1296

1297
    #[test]
1298
    pub fn test_expand() {
1299
        macrotest::expand("tests/expand/*.rs");
1300
        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1301
            let entry = entry.expect("Failed to read expand test file");
1302
            runtime_expand_attr(&entry);
1303
            runtime_expand_func(&entry);
1304
            runtime_expand_derive(&entry);
1305
        }
1306
    }
1307

1308
    fn runtime_expand_attr(path: &PathBuf) {
1309
        dbg!(path);
1310
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1311
        runtime_macros::emulate_attributelike_macro_expansion(
1312
            file,
1313
            &[
1314
                ("php_class", php_class_internal as AttributeFn),
1315
                ("php_const", php_const_internal as AttributeFn),
1316
                ("php_enum", php_enum_internal as AttributeFn),
1317
                ("php_interface", php_interface_internal as AttributeFn),
1318
                ("php_extern", php_extern_internal as AttributeFn),
1319
                ("php_function", php_function_internal as AttributeFn),
1320
                ("php_impl", php_impl_internal as AttributeFn),
1321
                ("php_module", php_module_internal as AttributeFn),
1322
            ],
1323
        )
1324
        .expect("Failed to expand attribute macros in test file");
1325
    }
1326

1327
    fn runtime_expand_func(path: &PathBuf) {
1328
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1329
        runtime_macros::emulate_functionlike_macro_expansion(
1330
            file,
1331
            &[
1332
                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1333
                ("wrap_function", wrap_function_internal as FunctionLikeFn),
1334
                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1335
            ],
1336
        )
1337
        .expect("Failed to expand function-like macros in test file");
1338
    }
1339

1340
    fn runtime_expand_derive(path: &PathBuf) {
1341
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
1342
        runtime_macros::emulate_derive_macro_expansion(
1343
            file,
1344
            &[("ZvalConvert", zval_convert_derive_internal)],
1345
        )
1346
        .expect("Failed to expand derive macros in test file");
1347
    }
1348
}
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