Skip to content

Commit 8f43b74

Browse files
authored
fix(class)!: generate correct stubs for extends and implements
* fix(class)!: generate correct stubs for extends and implements BREAKING CHANGE: `extends` and `implements` attributes now require the `stub` property containing the class/interface name to be used in stubs. Refs: #326 * test(class): add extends and implements tests Refs: #326
1 parent 7e4062a commit 8f43b74

File tree

18 files changed

+1268
-84
lines changed

18 files changed

+1268
-84
lines changed

crates/macros/src/class.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ pub struct StructAttributes {
2020
modifier: Option<syn::Ident>,
2121
/// An expression of `ClassFlags` to be applied to the class.
2222
flags: Option<syn::Expr>,
23-
extends: Option<syn::Expr>,
23+
extends: Option<ClassEntryAttribute>,
2424
#[darling(multiple)]
25-
implements: Vec<syn::Expr>,
25+
implements: Vec<ClassEntryAttribute>,
2626
attrs: Vec<Attribute>,
2727
}
2828

29+
#[derive(FromMeta, Debug)]
30+
pub struct ClassEntryAttribute {
31+
ce: syn::Expr,
32+
stub: String,
33+
}
34+
2935
pub fn parser(mut input: ItemStruct) -> Result<TokenStream> {
3036
let attr = StructAttributes::from_attributes(&input.attrs)?;
3137
let ident = &input.ident;
@@ -111,14 +117,13 @@ fn generate_registered_class_impl(
111117
ident: &syn::Ident,
112118
class_name: &str,
113119
modifier: Option<&syn::Ident>,
114-
extends: Option<&syn::Expr>,
115-
implements: &[syn::Expr],
120+
extends: Option<&ClassEntryAttribute>,
121+
implements: &[ClassEntryAttribute],
116122
fields: &[Property],
117123
flags: Option<&syn::Expr>,
118124
docs: &[String],
119125
) -> TokenStream {
120126
let modifier = modifier.option_tokens();
121-
let extends = extends.option_tokens();
122127

123128
let fields = fields.iter().map(|prop| {
124129
let name = prop.name();
@@ -149,16 +154,34 @@ fn generate_registered_class_impl(
149154
#(#docs)*
150155
};
151156

157+
let extends = if let Some(extends) = extends {
158+
let ce = &extends.ce;
159+
let stub = &extends.stub;
160+
quote! {
161+
Some((#ce, #stub))
162+
}
163+
} else {
164+
quote! { None }
165+
};
166+
167+
let implements = implements.iter().map(|imp| {
168+
let ce = &imp.ce;
169+
let stub = &imp.stub;
170+
quote! {
171+
(#ce, #stub)
172+
}
173+
});
174+
152175
quote! {
153176
impl ::ext_php_rs::class::RegisteredClass for #ident {
154177
const CLASS_NAME: &'static str = #class_name;
155178
const BUILDER_MODIFIER: ::std::option::Option<
156179
fn(::ext_php_rs::builders::ClassBuilder) -> ::ext_php_rs::builders::ClassBuilder
157180
> = #modifier;
158181
const EXTENDS: ::std::option::Option<
159-
fn() -> &'static ::ext_php_rs::zend::ClassEntry
182+
::ext_php_rs::class::ClassEntryInfo
160183
> = #extends;
161-
const IMPLEMENTS: &'static [fn() -> &'static ::ext_php_rs::zend::ClassEntry] = &[
184+
const IMPLEMENTS: &'static [::ext_php_rs::class::ClassEntryInfo] = &[
162185
#(#implements,)*
163186
];
164187
const FLAGS: ::ext_php_rs::flags::ClassFlags = #flags;

crates/macros/src/lib.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,19 @@ extern crate proc_macro;
2727
///
2828
/// ## Options
2929
///
30-
/// The attribute takes some options to modify the output of the class:
30+
/// There are additional macros that modify the class. These macros **must** be
31+
/// placed underneath the `#[php_class]` attribute.
3132
///
3233
/// - `name` - Changes the name of the class when exported to PHP. The Rust
3334
/// struct name is kept the same. If no name is given, the name of the struct
3435
/// is used. Useful for namespacing classes.
35-
///
36-
/// There are also additional macros that modify the class. These macros
37-
/// **must** be placed underneath the `#[php_class]` attribute.
38-
///
39-
/// - `#[php(extends = ce)]` - Sets the parent class of the class. Can only be
40-
/// used once. `ce` must be a function with the signature `fn() -> &'static
41-
/// ClassEntry`.
42-
/// - `#[php(implements = ce)]` - Implements the given interface on the class.
43-
/// Can be used multiple times. `ce` must be a valid function with the
44-
/// signature `fn() -> &'static ClassEntry`.
36+
/// - `rename` - Changes the case of the class name when exported to PHP.
37+
/// - `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - Sets the parent
38+
/// class of the class. Can only be used once. `ce_fn` must be a function with
39+
/// the signature `fn() -> &'static ClassEntry`.
40+
/// - `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the
41+
/// given interface on the class. Can be used multiple times. `ce_fn` must be
42+
/// a valid function with the signature `fn() -> &'static ClassEntry`.
4543
///
4644
/// You may also use the `#[php(prop)]` attribute on a struct field to use the
4745
/// field as a PHP property. By default, the field will be accessible from PHP
@@ -122,8 +120,9 @@ extern crate proc_macro;
122120
/// zend::ce
123121
/// };
124122
///
125-
/// #[php_class(name = "Redis\\Exception\\RedisException")]
126-
/// #[php(extends = ce::exception)]
123+
/// #[php_class]
124+
/// #[php(name = "Redis\\Exception\\RedisException")]
125+
/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
127126
/// #[derive(Default)]
128127
/// pub struct RedisException;
129128
///
@@ -144,8 +143,8 @@ extern crate proc_macro;
144143
///
145144
/// ## Implementing an Interface
146145
///
147-
/// To implement an interface, use `#[php(implements = ce)]` where `ce` is an
148-
/// function returning a `ClassEntry`. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
146+
/// To implement an interface, use `#[php(implements(ce = ce_fn, stub =
147+
/// "InterfaceName")]` where `ce_fn` is an function returning a `ClassEntry`. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
149148
///
150149
/// ````rust,no_run,ignore
151150
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
@@ -158,7 +157,7 @@ extern crate proc_macro;
158157
/// };
159158
///
160159
/// #[php_class]
161-
/// #[php(implements = ce::arrayaccess)]
160+
/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
162161
/// #[derive(Default)]
163162
/// pub struct EvenNumbersArray;
164163
///

guide/src/macros/classes.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ placed underneath the `#[php_class]` attribute.
1313
name is kept the same. If no name is given, the name of the struct is used.
1414
Useful for namespacing classes.
1515
- `rename` - Changes the case of the class name when exported to PHP.
16-
- `#[php(extends = ce)]` - Sets the parent class of the class. Can only be used once.
17-
`ce` must be a function with the signature `fn() -> &'static ClassEntry`.
18-
- `#[php(implements = ce)]` - Implements the given interface on the class. Can be used
19-
multiple times. `ce` must be a valid function with the signature
16+
- `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - Sets the parent class of the class. Can only be used once.
17+
`ce_fn` must be a function with the signature `fn() -> &'static ClassEntry`.
18+
- `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the given interface on the class. Can be used
19+
multiple times. `ce_fn` must be a valid function with the signature
2020
`fn() -> &'static ClassEntry`.
2121

2222
You may also use the `#[php(prop)]` attribute on a struct field to use the field as a
@@ -97,7 +97,7 @@ use ext_php_rs::{
9797
9898
#[php_class]
9999
#[php(name = "Redis\\Exception\\RedisException")]
100-
#[php(extends = ce::exception)]
100+
#[php(extends(ce = ce::exception, stub = "\\Exception"))]
101101
#[derive(Default)]
102102
pub struct RedisException;
103103
@@ -118,7 +118,7 @@ pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
118118

119119
## Implementing an Interface
120120

121-
To implement an interface, use `#[php(implements = ce)]` where `ce` is an function returning a `ClassEntry`.
121+
To implement an interface, use `#[php(implements(ce = ce_fn, stub = "InterfaceName")]` where `ce_fn` is an function returning a `ClassEntry`.
122122
The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
123123

124124
````rust,no_run
@@ -132,7 +132,7 @@ use ext_php_rs::{
132132
};
133133
134134
#[php_class]
135-
#[php(implements = ce::arrayaccess)]
135+
#[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
136136
#[derive(Default)]
137137
pub struct EvenNumbersArray;
138138

guide/src/migration-guides/v0.14.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
106106
- `#[php(name = "NEW_NAME")]` - Renames the class
107107
- `#[php(rename = case)]` - Changes the case of the class name
108108
- `#[php(vis = "public")]` - Changes the visibility of the class
109-
- `#[php(extends = "ParentClass")]` - Extends a parent class
110-
- `#[php(implements = "Interface")]` - Implements an interface
109+
- `#[php(extends(ce = ce_fn, stub = "ParentClass")]` - Extends a parent class
110+
- `#[php(implements(ce = ce_fn, stub = "Interface"))]` - Implements an interface
111111
- `#[php(prop)]` - Marks a field as a property
112112

113113
**Supported `#[php]` attributes (`impl`):**
@@ -116,6 +116,13 @@ pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
116116

117117
For elements in the `#[php_impl]` block see the respective function and constant attributes.
118118

119+
#### Extends and Implements
120+
121+
Extends and implements are now taking a second parameter which is the
122+
`stub` name. This is the name of the class or interface in PHP.
123+
124+
This value is only used for stub generation and is not used for the class name in Rust.
125+
119126
### Constants
120127

121128
Mostly unchanged in terms of constant definition, however you now need to

0 commit comments

Comments
 (0)