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