Skip to content

Commit d32957c

Browse files
committed
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
1 parent 7e4062a commit d32957c

File tree

11 files changed

+94
-56
lines changed

11 files changed

+94
-56
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

src/builders/class.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ type ConstantEntry = (String, Box<dyn FnOnce() -> Result<Zval>>, DocComments);
2424
pub struct ClassBuilder {
2525
pub(crate) name: String,
2626
ce: ClassEntry,
27-
extends: Option<&'static ClassEntry>,
28-
interfaces: Vec<&'static ClassEntry>,
27+
pub(crate) extends: Option<(&'static ClassEntry, &'static str)>,
28+
pub(crate) interfaces: Vec<(&'static ClassEntry, &'static str)>,
2929
pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
3030
object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
3131
pub(crate) properties: Vec<(String, PropertyFlags, DocComments)>,
@@ -63,8 +63,8 @@ impl ClassBuilder {
6363
/// # Parameters
6464
///
6565
/// * `parent` - The parent class to extend.
66-
pub fn extends(mut self, parent: &'static ClassEntry) -> Self {
67-
self.extends = Some(parent);
66+
pub fn extends(mut self, parent: &'static ClassEntry, stub: &'static str) -> Self {
67+
self.extends = Some((parent, stub));
6868
self
6969
}
7070

@@ -77,12 +77,12 @@ impl ClassBuilder {
7777
/// # Panics
7878
///
7979
/// Panics when the given class entry `interface` is not an interface.
80-
pub fn implements(mut self, interface: &'static ClassEntry) -> Self {
80+
pub fn implements(mut self, interface: &'static ClassEntry, stub: &'static str) -> Self {
8181
assert!(
8282
interface.is_interface(),
8383
"Given class entry was not an interface."
8484
);
85-
self.interfaces.push(interface);
85+
self.interfaces.push((interface, stub));
8686
self
8787
}
8888

@@ -309,7 +309,7 @@ impl ClassBuilder {
309309
zend_register_internal_class_ex(
310310
&mut self.ce,
311311
match self.extends {
312-
Some(ptr) => ptr::from_ref(ptr).cast_mut(),
312+
Some((ptr, _)) => ptr::from_ref(ptr).cast_mut(),
313313
None => std::ptr::null_mut(),
314314
},
315315
)
@@ -329,7 +329,7 @@ impl ClassBuilder {
329329
}
330330
}
331331

332-
for iface in self.interfaces {
332+
for (iface, _) in self.interfaces {
333333
unsafe { zend_do_implement_interface(class, ptr::from_ref(iface).cast_mut()) };
334334
}
335335

src/builders/module.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@ impl ModuleBuilder<'_> {
179179
for (method, flags) in T::method_builders() {
180180
builder = builder.method(method, flags);
181181
}
182-
if let Some(extends) = T::EXTENDS {
183-
builder = builder.extends(extends());
182+
if let Some((extends, stub)) = T::EXTENDS {
183+
builder = builder.extends(extends(), stub);
184184
}
185-
for iface in T::IMPLEMENTS {
186-
builder = builder.implements(iface());
185+
for (iface, stub) in T::IMPLEMENTS {
186+
builder = builder.implements(iface(), stub);
187187
}
188188
for (name, value, docs) in T::constants() {
189189
builder = builder

src/class.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ use crate::{
1818
zend::{ClassEntry, ExecuteData, ZendObjectHandlers},
1919
};
2020

21+
/// A type alias for a tuple containing a function pointer to a class entry
22+
/// and a string representing the class name used in stubs.
23+
pub type ClassEntryInfo = (fn() -> &'static ClassEntry, &'static str);
24+
2125
/// Implemented on Rust types which are exported to PHP. Allows users to get and
2226
/// set PHP properties on the object.
2327
pub trait RegisteredClass: Sized + 'static {
@@ -29,10 +33,10 @@ pub trait RegisteredClass: Sized + 'static {
2933
const BUILDER_MODIFIER: Option<fn(ClassBuilder) -> ClassBuilder>;
3034

3135
/// Parent class entry. Optional.
32-
const EXTENDS: Option<fn() -> &'static ClassEntry>;
36+
const EXTENDS: Option<ClassEntryInfo>;
3337

3438
/// Interfaces implemented by the class.
35-
const IMPLEMENTS: &'static [fn() -> &'static ClassEntry];
39+
const IMPLEMENTS: &'static [ClassEntryInfo];
3640

3741
/// PHP flags applied to the class.
3842
const FLAGS: ClassFlags = ClassFlags::empty();

src/closure.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use std::collections::HashMap;
55
use crate::{
66
args::{Arg, ArgParser},
77
builders::{ClassBuilder, FunctionBuilder},
8-
class::{ClassMetadata, RegisteredClass},
8+
class::{ClassEntryInfo, ClassMetadata, RegisteredClass},
99
convert::{FromZval, IntoZval},
1010
describe::DocComments,
1111
exception::PhpException,
1212
flags::{DataType, MethodFlags},
1313
internal::property::PropertyInfo,
1414
types::Zval,
15-
zend::{ClassEntry, ExecuteData},
15+
zend::ExecuteData,
1616
zend_fastcall,
1717
};
1818

@@ -150,8 +150,8 @@ impl RegisteredClass for Closure {
150150
const CLASS_NAME: &'static str = "RustClosure";
151151

152152
const BUILDER_MODIFIER: Option<fn(ClassBuilder) -> ClassBuilder> = None;
153-
const EXTENDS: Option<fn() -> &'static ClassEntry> = None;
154-
const IMPLEMENTS: &'static [fn() -> &'static ClassEntry] = &[];
153+
const EXTENDS: Option<ClassEntryInfo> = None;
154+
const IMPLEMENTS: &'static [ClassEntryInfo] = &[];
155155

156156
fn get_metadata() -> &'static ClassMetadata<Self> {
157157
&CLOSURE_META

src/describe/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,13 @@ impl From<ClassBuilder> for Class {
185185
.collect::<StdVec<_>>()
186186
.into(),
187187
),
188-
extends: abi::Option::None, // TODO: Implement extends #326
189-
implements: vec![].into(), // TODO: Implement implements #326
188+
extends: val.extends.map(|(_, stub)| stub.into()).into(),
189+
implements: val
190+
.interfaces
191+
.into_iter()
192+
.map(|(_, stub)| stub.into())
193+
.collect::<StdVec<_>>()
194+
.into(),
190195
properties: val
191196
.properties
192197
.into_iter()

src/exception.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()>
213213
/// use crate::ext_php_rs::convert::IntoZval;
214214
///
215215
/// #[php_class]
216-
/// #[php(extends = ext_php_rs::zend::ce::exception)]
216+
/// #[php(extends(ce = ext_php_rs::zend::ce::exception, stub = "\\Exception"))]
217217
/// pub struct JsException {
218218
/// #[php(prop, flags = ext_php_rs::flags::PropertyFlags::Public)]
219219
/// message: String,

0 commit comments

Comments
 (0)