ext_php_rs/describe/
mod.rs

1//! Types used to describe downstream extensions. Used by the `cargo-php`
2//! CLI application to generate PHP stub files used by IDEs.
3use std::vec::Vec as StdVec;
4
5use crate::{
6    builders::{ClassBuilder, FunctionBuilder},
7    constant::IntoConst,
8    flags::{DataType, MethodFlags, PropertyFlags},
9    prelude::ModuleBuilder,
10};
11use abi::{Option, RString, Str, Vec};
12
13pub mod abi;
14mod stub;
15
16pub use stub::ToStub;
17
18/// A slice of strings containing documentation comments.
19pub type DocComments = &'static [&'static str];
20
21/// Representation of the extension used to generate PHP stubs.
22#[repr(C)]
23pub struct Description {
24    /// Extension description.
25    pub module: Module,
26    /// ext-php-rs version.
27    pub version: &'static str,
28}
29
30impl Description {
31    /// Creates a new description.
32    ///
33    /// # Parameters
34    ///
35    /// * `module` - The extension module representation.
36    #[must_use]
37    pub fn new(module: Module) -> Self {
38        Self {
39            module,
40            version: crate::VERSION,
41        }
42    }
43}
44
45/// Represents a set of comments on an export.
46#[repr(C)]
47#[derive(Debug, PartialEq)]
48pub struct DocBlock(pub Vec<Str>);
49
50impl From<&'static [&'static str]> for DocBlock {
51    fn from(val: &'static [&'static str]) -> Self {
52        Self(
53            val.iter()
54                .map(|s| (*s).into())
55                .collect::<StdVec<_>>()
56                .into(),
57        )
58    }
59}
60
61/// Represents an extension containing a set of exports.
62#[repr(C)]
63pub struct Module {
64    /// Name of the extension.
65    pub name: RString,
66    /// Functions exported by the extension.
67    pub functions: Vec<Function>,
68    /// Classes exported by the extension.
69    pub classes: Vec<Class>,
70    /// Constants exported by the extension.
71    pub constants: Vec<Constant>,
72}
73
74/// Builds a [`Module`] from a [`ModuleBuilder`].
75/// This is used to generate the PHP stubs for the module.
76impl From<ModuleBuilder<'_>> for Module {
77    fn from(builder: ModuleBuilder) -> Self {
78        let functions = builder.functions;
79        Self {
80            name: builder.name.into(),
81            functions: functions
82                .into_iter()
83                .map(Function::from)
84                .collect::<StdVec<_>>()
85                .into(),
86            classes: builder
87                .classes
88                .into_iter()
89                .map(|c| c().into())
90                .collect::<StdVec<_>>()
91                .into(),
92            constants: builder
93                .constants
94                .into_iter()
95                .map(Constant::from)
96                .collect::<StdVec<_>>()
97                .into(),
98        }
99    }
100}
101
102/// Represents an exported function.
103#[repr(C)]
104pub struct Function {
105    /// Name of the function.
106    pub name: RString,
107    /// Documentation comments for the function.
108    pub docs: DocBlock,
109    /// Return value of the function.
110    pub ret: Option<Retval>,
111    /// Parameters of the function.
112    pub params: Vec<Parameter>,
113}
114
115impl From<FunctionBuilder<'_>> for Function {
116    fn from(val: FunctionBuilder<'_>) -> Self {
117        let ret_allow_null = val.ret_as_null;
118        Function {
119            name: val.name.into(),
120            docs: DocBlock(
121                val.docs
122                    .iter()
123                    .map(|d| (*d).into())
124                    .collect::<StdVec<_>>()
125                    .into(),
126            ),
127            ret: val
128                .retval
129                .map(|r| Retval {
130                    ty: r,
131                    nullable: r != DataType::Mixed && ret_allow_null,
132                })
133                .into(),
134            params: val
135                .args
136                .into_iter()
137                .map(Parameter::from)
138                .collect::<StdVec<_>>()
139                .into(),
140        }
141    }
142}
143
144/// Represents a parameter attached to an exported function or method.
145#[repr(C)]
146#[derive(Debug, PartialEq)]
147pub struct Parameter {
148    /// Name of the parameter.
149    pub name: RString,
150    /// Type of the parameter.
151    pub ty: Option<DataType>,
152    /// Whether the parameter is nullable.
153    pub nullable: bool,
154    /// Default value of the parameter.
155    pub default: Option<RString>,
156}
157
158/// Represents an exported class.
159#[repr(C)]
160pub struct Class {
161    /// Name of the class.
162    pub name: RString,
163    /// Documentation comments for the class.
164    pub docs: DocBlock,
165    /// Name of the class the exported class extends. (Not implemented #326)
166    pub extends: Option<RString>,
167    /// Names of the interfaces the exported class implements. (Not implemented
168    /// #326)
169    pub implements: Vec<RString>,
170    /// Properties of the class.
171    pub properties: Vec<Property>,
172    /// Methods of the class.
173    pub methods: Vec<Method>,
174    /// Constants of the class.
175    pub constants: Vec<Constant>,
176}
177
178impl From<ClassBuilder> for Class {
179    fn from(val: ClassBuilder) -> Self {
180        Self {
181            name: val.name.into(),
182            docs: DocBlock(
183                val.docs
184                    .iter()
185                    .map(|doc| (*doc).into())
186                    .collect::<StdVec<_>>()
187                    .into(),
188            ),
189            extends: val.extends.map(|(_, stub)| stub.into()).into(),
190            implements: val
191                .interfaces
192                .into_iter()
193                .map(|(_, stub)| stub.into())
194                .collect::<StdVec<_>>()
195                .into(),
196            properties: val
197                .properties
198                .into_iter()
199                .map(Property::from)
200                .collect::<StdVec<_>>()
201                .into(),
202            methods: val
203                .methods
204                .into_iter()
205                .map(Method::from)
206                .collect::<StdVec<_>>()
207                .into(),
208            constants: val
209                .constants
210                .into_iter()
211                .map(|(name, _, docs)| (name, docs))
212                .map(Constant::from)
213                .collect::<StdVec<_>>()
214                .into(),
215        }
216    }
217}
218
219/// Represents a property attached to an exported class.
220#[repr(C)]
221#[derive(Debug, PartialEq)]
222pub struct Property {
223    /// Name of the property.
224    pub name: RString,
225    /// Documentation comments for the property.
226    pub docs: DocBlock,
227    /// Type of the property (Not implemented #376)
228    pub ty: Option<DataType>,
229    /// Visibility of the property.
230    pub vis: Visibility,
231    /// Whether the property is static.
232    pub static_: bool,
233    /// Whether the property is nullable. (Not implemented #376)
234    pub nullable: bool,
235    /// Default value of the property. (Not implemented #376)
236    pub default: Option<RString>,
237}
238
239impl From<(String, PropertyFlags, DocComments)> for Property {
240    fn from(value: (String, PropertyFlags, DocComments)) -> Self {
241        let (name, flags, docs) = value;
242        let static_ = flags.contains(PropertyFlags::Static);
243        let vis = Visibility::from(flags);
244        // TODO: Implement ty #376
245        let ty = abi::Option::None;
246        // TODO: Implement default #376
247        let default = abi::Option::<abi::RString>::None;
248        // TODO: Implement nullable #376
249        let nullable = false;
250        let docs = docs.into();
251
252        Self {
253            name: name.into(),
254            docs,
255            ty,
256            vis,
257            static_,
258            nullable,
259            default,
260        }
261    }
262}
263
264/// Represents a method attached to an exported class.
265#[repr(C)]
266#[derive(Debug, PartialEq)]
267pub struct Method {
268    /// Name of the method.
269    pub name: RString,
270    /// Documentation comments for the method.
271    pub docs: DocBlock,
272    /// Type of the method.
273    pub ty: MethodType,
274    /// Parameters of the method.
275    pub params: Vec<Parameter>,
276    /// Return value of the method.
277    pub retval: Option<Retval>,
278    /// Whether the method is static.
279    pub r#static: bool,
280    /// Visibility of the method.
281    pub visibility: Visibility,
282}
283
284impl From<(FunctionBuilder<'_>, MethodFlags)> for Method {
285    fn from(val: (FunctionBuilder<'_>, MethodFlags)) -> Self {
286        let (builder, flags) = val;
287        let ret_allow_null = builder.ret_as_null;
288        Method {
289            name: builder.name.into(),
290            docs: DocBlock(
291                builder
292                    .docs
293                    .iter()
294                    .map(|d| (*d).into())
295                    .collect::<StdVec<_>>()
296                    .into(),
297            ),
298            retval: builder
299                .retval
300                .map(|r| Retval {
301                    ty: r,
302                    nullable: r != DataType::Mixed && ret_allow_null,
303                })
304                .into(),
305            params: builder
306                .args
307                .into_iter()
308                .map(Into::into)
309                .collect::<StdVec<_>>()
310                .into(),
311            ty: flags.into(),
312            r#static: flags.contains(MethodFlags::Static),
313            visibility: flags.into(),
314        }
315    }
316}
317
318/// Represents a value returned from a function or method.
319#[repr(C)]
320#[derive(Debug, PartialEq)]
321pub struct Retval {
322    /// Type of the return value.
323    pub ty: DataType,
324    /// Whether the return value is nullable.
325    pub nullable: bool,
326}
327
328/// Enumerator used to differentiate between methods.
329#[repr(C)]
330#[derive(Clone, Copy, Debug, PartialEq)]
331pub enum MethodType {
332    /// A member method.
333    Member,
334    /// A static method.
335    Static,
336    /// A constructor.
337    Constructor,
338}
339
340impl From<MethodFlags> for MethodType {
341    fn from(value: MethodFlags) -> Self {
342        if value.contains(MethodFlags::IsConstructor) {
343            return Self::Constructor;
344        }
345        if value.contains(MethodFlags::Static) {
346            return Self::Static;
347        }
348
349        Self::Member
350    }
351}
352
353/// Enumerator used to differentiate between different method and property
354/// visibilties.
355#[repr(C)]
356#[derive(Clone, Copy, Debug, PartialEq)]
357pub enum Visibility {
358    /// Private visibility.
359    Private,
360    /// Protected visibility.
361    Protected,
362    /// Public visibility.
363    Public,
364}
365
366impl From<PropertyFlags> for Visibility {
367    fn from(value: PropertyFlags) -> Self {
368        if value.contains(PropertyFlags::Protected) {
369            return Self::Protected;
370        }
371        if value.contains(PropertyFlags::Private) {
372            return Self::Private;
373        }
374
375        Self::Public
376    }
377}
378
379impl From<MethodFlags> for Visibility {
380    fn from(value: MethodFlags) -> Self {
381        if value.contains(MethodFlags::Protected) {
382            return Self::Protected;
383        }
384
385        if value.contains(MethodFlags::Private) {
386            return Self::Private;
387        }
388
389        Self::Public
390    }
391}
392
393/// Represents an exported constant, stand alone or attached to a class.
394#[repr(C)]
395pub struct Constant {
396    /// Name of the constant.
397    pub name: RString,
398    /// Documentation comments for the constant.
399    pub docs: DocBlock,
400    /// Value of the constant.
401    pub value: Option<RString>,
402}
403
404impl From<(String, DocComments)> for Constant {
405    fn from(val: (String, DocComments)) -> Self {
406        let (name, docs) = val;
407        Constant {
408            name: name.into(),
409            value: abi::Option::None,
410            docs: docs.into(),
411        }
412    }
413}
414
415impl From<(String, Box<dyn IntoConst + Send>, DocComments)> for Constant {
416    fn from(val: (String, Box<dyn IntoConst + Send + 'static>, DocComments)) -> Self {
417        let (name, _, docs) = val;
418        Constant {
419            name: name.into(),
420            value: abi::Option::None,
421            docs: docs.into(),
422        }
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    #![cfg_attr(windows, feature(abi_vectorcall))]
429    use super::*;
430
431    use crate::{args::Arg, test::test_function};
432
433    #[test]
434    fn test_new_description() {
435        let module = Module {
436            name: "test".into(),
437            functions: vec![].into(),
438            classes: vec![].into(),
439            constants: vec![].into(),
440        };
441
442        let description = Description::new(module);
443        assert_eq!(description.version, crate::VERSION);
444        assert_eq!(description.module.name, "test".into());
445    }
446
447    #[test]
448    fn test_doc_block_from() {
449        let docs: &'static [&'static str] = &["doc1", "doc2"];
450        let docs: DocBlock = docs.into();
451        assert_eq!(docs.0.len(), 2);
452        assert_eq!(docs.0[0], "doc1".into());
453        assert_eq!(docs.0[1], "doc2".into());
454    }
455
456    #[test]
457    fn test_module_from() {
458        let builder = ModuleBuilder::new("test", "test_version")
459            .function(FunctionBuilder::new("test_function", test_function));
460        let module: Module = builder.into();
461        assert_eq!(module.name, "test".into());
462        assert_eq!(module.functions.len(), 1);
463        assert_eq!(module.classes.len(), 0);
464        assert_eq!(module.constants.len(), 0);
465    }
466
467    #[test]
468    fn test_function_from() {
469        let builder = FunctionBuilder::new("test_function", test_function)
470            .docs(&["doc1", "doc2"])
471            .arg(Arg::new("foo", DataType::Long))
472            .returns(DataType::Bool, true, true);
473        let function: Function = builder.into();
474        assert_eq!(function.name, "test_function".into());
475        assert_eq!(function.docs.0.len(), 2);
476        assert_eq!(
477            function.params,
478            vec![Parameter {
479                name: "foo".into(),
480                ty: Option::Some(DataType::Long),
481                nullable: false,
482                default: Option::None,
483            }]
484            .into()
485        );
486        assert_eq!(
487            function.ret,
488            Option::Some(Retval {
489                ty: DataType::Bool,
490                nullable: true,
491            })
492        );
493    }
494
495    #[test]
496    fn test_class_from() {
497        let builder = ClassBuilder::new("TestClass")
498            .docs(&["doc1", "doc2"])
499            .extends((|| todo!(), "BaseClass"))
500            .implements((|| todo!(), "Interface1"))
501            .implements((|| todo!(), "Interface2"))
502            .property("prop1", PropertyFlags::Public, &["doc1"])
503            .method(
504                FunctionBuilder::new("test_function", test_function),
505                MethodFlags::Protected,
506            );
507        let class: Class = builder.into();
508
509        assert_eq!(class.name, "TestClass".into());
510        assert_eq!(class.docs.0.len(), 2);
511        assert_eq!(class.extends, Option::Some("BaseClass".into()));
512        assert_eq!(
513            class.implements,
514            vec!["Interface1".into(), "Interface2".into()].into()
515        );
516        assert_eq!(class.properties.len(), 1);
517        assert_eq!(
518            class.properties[0],
519            Property {
520                name: "prop1".into(),
521                docs: DocBlock(vec!["doc1".into()].into()),
522                ty: Option::None,
523                vis: Visibility::Public,
524                static_: false,
525                nullable: false,
526                default: Option::None,
527            }
528        );
529        assert_eq!(class.methods.len(), 1);
530        assert_eq!(
531            class.methods[0],
532            Method {
533                name: "test_function".into(),
534                docs: DocBlock(vec![].into()),
535                ty: MethodType::Member,
536                params: vec![].into(),
537                retval: Option::None,
538                r#static: false,
539                visibility: Visibility::Protected,
540            }
541        );
542    }
543
544    #[test]
545    fn test_property_from() {
546        let docs: &'static [&'static str] = &["doc1", "doc2"];
547        let property: Property =
548            ("test_property".to_string(), PropertyFlags::Protected, docs).into();
549        assert_eq!(property.name, "test_property".into());
550        assert_eq!(property.docs.0.len(), 2);
551        assert_eq!(property.vis, Visibility::Protected);
552        assert!(!property.static_);
553        assert!(!property.nullable);
554    }
555
556    #[test]
557    fn test_method_from() {
558        let builder = FunctionBuilder::new("test_method", test_function)
559            .docs(&["doc1", "doc2"])
560            .arg(Arg::new("foo", DataType::Long))
561            .returns(DataType::Bool, true, true);
562        let method: Method = (builder, MethodFlags::Static | MethodFlags::Protected).into();
563        assert_eq!(method.name, "test_method".into());
564        assert_eq!(method.docs.0.len(), 2);
565        assert_eq!(
566            method.params,
567            vec![Parameter {
568                name: "foo".into(),
569                ty: Option::Some(DataType::Long),
570                nullable: false,
571                default: Option::None,
572            }]
573            .into()
574        );
575        assert_eq!(
576            method.retval,
577            Option::Some(Retval {
578                ty: DataType::Bool,
579                nullable: true,
580            })
581        );
582        assert!(method.r#static);
583        assert_eq!(method.visibility, Visibility::Protected);
584        assert_eq!(method.ty, MethodType::Static);
585    }
586
587    #[test]
588    fn test_ty_from() {
589        let r#static: MethodType = MethodFlags::Static.into();
590        assert_eq!(r#static, MethodType::Static);
591
592        let constructor: MethodType = MethodFlags::IsConstructor.into();
593        assert_eq!(constructor, MethodType::Constructor);
594
595        let member: MethodType = MethodFlags::Public.into();
596        assert_eq!(member, MethodType::Member);
597
598        let mixed: MethodType = (MethodFlags::Protected | MethodFlags::Static).into();
599        assert_eq!(mixed, MethodType::Static);
600
601        let both: MethodType = (MethodFlags::Static | MethodFlags::IsConstructor).into();
602        assert_eq!(both, MethodType::Constructor);
603
604        let empty: MethodType = MethodFlags::empty().into();
605        assert_eq!(empty, MethodType::Member);
606    }
607
608    #[test]
609    fn test_prop_visibility_from() {
610        let private: Visibility = PropertyFlags::Private.into();
611        assert_eq!(private, Visibility::Private);
612
613        let protected: Visibility = PropertyFlags::Protected.into();
614        assert_eq!(protected, Visibility::Protected);
615
616        let public: Visibility = PropertyFlags::Public.into();
617        assert_eq!(public, Visibility::Public);
618
619        let mixed: Visibility = (PropertyFlags::Protected | PropertyFlags::Static).into();
620        assert_eq!(mixed, Visibility::Protected);
621
622        let empty: Visibility = PropertyFlags::empty().into();
623        assert_eq!(empty, Visibility::Public);
624    }
625
626    #[test]
627    fn test_method_visibility_from() {
628        let private: Visibility = MethodFlags::Private.into();
629        assert_eq!(private, Visibility::Private);
630
631        let protected: Visibility = MethodFlags::Protected.into();
632        assert_eq!(protected, Visibility::Protected);
633
634        let public: Visibility = MethodFlags::Public.into();
635        assert_eq!(public, Visibility::Public);
636
637        let mixed: Visibility = (MethodFlags::Protected | MethodFlags::Static).into();
638        assert_eq!(mixed, Visibility::Protected);
639
640        let empty: Visibility = MethodFlags::empty().into();
641        assert_eq!(empty, Visibility::Public);
642    }
643}