mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
pyclass member support for #[pymodule]
This commit is contained in:
1 parent
7cea2c3953
commit
06f8d0bb98
4 files changed
+119
-91
No files matched your search
+36
-31
@@ -1,11 +1,11 @@
|
||||
use super::Diagnostic;
|
||||
use crate::util::{def_to_name, path_eq, strip_prefix, ItemMeta};
|
||||
use crate::util::{def_to_name, path_eq, strip_prefix, ItemIdent, ItemMeta};
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Index, Item, Lit, Meta,
|
||||
NestedMeta, Signature,
|
||||
NestedMeta,
|
||||
};
|
||||
|
||||
fn meta_to_vec(meta: Meta) -> Result<Vec<NestedMeta>, Meta> {
|
||||
@@ -54,7 +54,7 @@ impl Class {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_method(sig: &Signature, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
fn extract_method(ident: &Ident, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| {
|
||||
err_span!(
|
||||
meta,
|
||||
@@ -64,14 +64,14 @@ impl Class {
|
||||
})?;
|
||||
|
||||
let item_meta =
|
||||
ItemMeta::from_nested_meta("pymethod", sig, &nesteds, ItemMeta::ATTRIBUTE_NAMES)?;
|
||||
ItemMeta::from_nested_meta("pymethod", &ident, &nesteds, ItemMeta::ATTRIBUTE_NAMES)?;
|
||||
Ok(ClassItem::Method {
|
||||
item_ident: sig.ident.clone(),
|
||||
item_ident: ident.clone(),
|
||||
py_name: item_meta.method_name()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_classmethod(sig: &Signature, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
fn extract_classmethod(ident: &Ident, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| {
|
||||
err_span!(
|
||||
meta,
|
||||
@@ -79,15 +79,19 @@ impl Class {
|
||||
#[pyclassmethod(name = \"...\")]",
|
||||
)
|
||||
})?;
|
||||
let item_meta =
|
||||
ItemMeta::from_nested_meta("pyclassmethod", sig, &nesteds, ItemMeta::ATTRIBUTE_NAMES)?;
|
||||
let item_meta = ItemMeta::from_nested_meta(
|
||||
"pyclassmethod",
|
||||
&ident,
|
||||
&nesteds,
|
||||
ItemMeta::ATTRIBUTE_NAMES,
|
||||
)?;
|
||||
Ok(ClassItem::ClassMethod {
|
||||
item_ident: sig.ident.clone(),
|
||||
item_ident: ident.clone(),
|
||||
py_name: item_meta.method_name()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_property(sig: &Signature, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
fn extract_property(ident: &Ident, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| {
|
||||
err_span!(
|
||||
meta,
|
||||
@@ -96,26 +100,26 @@ impl Class {
|
||||
)
|
||||
})?;
|
||||
let item_meta =
|
||||
ItemMeta::from_nested_meta("pyproperty", sig, &nesteds, ItemMeta::PROPERTY_NAMES)?;
|
||||
ItemMeta::from_nested_meta("pyproperty", &ident, &nesteds, ItemMeta::PROPERTY_NAMES)?;
|
||||
Ok(ClassItem::Property {
|
||||
py_name: item_meta.property_name()?,
|
||||
item_ident: sig.ident.clone(),
|
||||
item_ident: ident.clone(),
|
||||
setter: item_meta.setter()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_slot(sig: &Signature, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
fn extract_slot(ident: &Ident, meta: Meta) -> Result<ClassItem, Diagnostic> {
|
||||
let pyslot_err = "#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]";
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| err_span!(meta, "{}", pyslot_err))?;
|
||||
if nesteds.len() > 1 {
|
||||
return Err(Diagnostic::spanned_error("e!(#(#nesteds)*), pyslot_err));
|
||||
}
|
||||
let slot_ident = if nesteds.is_empty() {
|
||||
let ident_str = sig.ident.to_string();
|
||||
let ident_str = ident.to_string();
|
||||
if let Some(stripped) = strip_prefix(&ident_str, "tp_") {
|
||||
proc_macro2::Ident::new(stripped, sig.ident.span())
|
||||
proc_macro2::Ident::new(stripped, ident.span())
|
||||
} else {
|
||||
sig.ident.clone()
|
||||
ident.clone()
|
||||
}
|
||||
} else {
|
||||
match nesteds.into_iter().next().unwrap() {
|
||||
@@ -128,14 +132,14 @@ impl Class {
|
||||
};
|
||||
Ok(ClassItem::Slot {
|
||||
slot_ident,
|
||||
item_ident: sig.ident.clone(),
|
||||
item_ident: ident.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_item_from_syn(
|
||||
&mut self,
|
||||
attrs: &mut Vec<Attribute>,
|
||||
sig: &Signature,
|
||||
ident: &Ident,
|
||||
) -> Result<(), Diagnostic> {
|
||||
let mut attr_idxs = Vec::new();
|
||||
for (i, meta) in attrs
|
||||
@@ -149,10 +153,10 @@ impl Class {
|
||||
None => continue,
|
||||
};
|
||||
let item = match name.to_string().as_str() {
|
||||
"pymethod" => Self::extract_method(sig, meta)?,
|
||||
"pyclassmethod" => Self::extract_classmethod(sig, meta)?,
|
||||
"pyproperty" => Self::extract_property(sig, meta)?,
|
||||
"pyslot" => Self::extract_slot(sig, meta)?,
|
||||
"pymethod" => Self::extract_method(ident, meta)?,
|
||||
"pyclassmethod" => Self::extract_classmethod(ident, meta)?,
|
||||
"pyproperty" => Self::extract_property(ident, meta)?,
|
||||
"pyslot" => Self::extract_slot(ident, meta)?,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
@@ -177,12 +181,7 @@ impl Class {
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemSig<'a> {
|
||||
attrs: &'a mut Vec<Attribute>,
|
||||
sig: &'a Signature,
|
||||
}
|
||||
|
||||
fn extract_impl_items(mut items: Vec<ItemSig>) -> Result<TokenStream2, Diagnostic> {
|
||||
fn extract_impl_items(mut items: Vec<ItemIdent>) -> Result<TokenStream2, Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
let mut class = Class::default();
|
||||
@@ -190,7 +189,7 @@ fn extract_impl_items(mut items: Vec<ItemSig>) -> Result<TokenStream2, Diagnosti
|
||||
for item in items.iter_mut() {
|
||||
push_diag_result!(
|
||||
diagnostics,
|
||||
class.extract_item_from_syn(&mut item.attrs, item.sig),
|
||||
class.extract_item_from_syn(&mut item.attrs, &item.ident),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -356,7 +355,10 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream2, Diag
|
||||
.iter_mut()
|
||||
.filter_map(|item| match item {
|
||||
syn::ImplItem::Method(syn::ImplItemMethod { attrs, sig, .. }) => {
|
||||
Some(ItemSig { attrs, sig })
|
||||
Some(ItemIdent {
|
||||
attrs,
|
||||
ident: &sig.ident,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
@@ -386,7 +388,10 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream2, Diag
|
||||
.iter_mut()
|
||||
.filter_map(|item| match item {
|
||||
syn::TraitItem::Method(syn::TraitItemMethod { attrs, sig, .. }) => {
|
||||
Some(ItemSig { attrs, sig })
|
||||
Some(ItemIdent {
|
||||
attrs,
|
||||
ident: &sig.ident,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
+58
-30
@@ -1,12 +1,9 @@
|
||||
use super::Diagnostic;
|
||||
use crate::util::{def_to_name, ItemMeta};
|
||||
use crate::util::{def_to_name, ItemIdent, ItemMeta};
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, quote_spanned};
|
||||
use std::collections::HashSet;
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta,
|
||||
Signature,
|
||||
};
|
||||
use syn::{parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta};
|
||||
|
||||
fn meta_to_vec(meta: Meta) -> Result<Vec<NestedMeta>, Meta> {
|
||||
match meta {
|
||||
@@ -24,6 +21,7 @@ struct Module {
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
enum ModuleItem {
|
||||
Function { item_ident: Ident, py_name: String },
|
||||
Class { item_ident: Ident, py_name: String },
|
||||
}
|
||||
|
||||
impl Module {
|
||||
@@ -38,7 +36,7 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_function(sig: &Signature, meta: Meta) -> Result<ModuleItem, Diagnostic> {
|
||||
fn extract_function(ident: &Ident, meta: Meta) -> Result<ModuleItem, Diagnostic> {
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| {
|
||||
err_span!(
|
||||
meta,
|
||||
@@ -48,9 +46,26 @@ impl Module {
|
||||
})?;
|
||||
|
||||
let item_meta =
|
||||
ItemMeta::from_nested_meta("pyfunction", sig, &nesteds, ItemMeta::SIMPLE_NAMES)?;
|
||||
ItemMeta::from_nested_meta("pyfunction", &ident, &nesteds, ItemMeta::SIMPLE_NAMES)?;
|
||||
Ok(ModuleItem::Function {
|
||||
item_ident: sig.ident.clone(),
|
||||
item_ident: ident.clone(),
|
||||
py_name: item_meta.simple_name()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_class(ident: &Ident, meta: Meta) -> Result<ModuleItem, Diagnostic> {
|
||||
let nesteds = meta_to_vec(meta).map_err(|meta| {
|
||||
err_span!(
|
||||
meta,
|
||||
"#[pyclass = \"...\"] cannot be a name/value, you probably meant \
|
||||
#[pyclass(name = \"...\")]",
|
||||
)
|
||||
})?;
|
||||
|
||||
let item_meta =
|
||||
ItemMeta::from_nested_meta("pyclass", &ident, &nesteds, ItemMeta::SIMPLE_NAMES)?;
|
||||
Ok(ModuleItem::Class {
|
||||
item_ident: ident.clone(),
|
||||
py_name: item_meta.simple_name()?,
|
||||
})
|
||||
}
|
||||
@@ -58,7 +73,7 @@ impl Module {
|
||||
fn extract_item_from_syn(
|
||||
&mut self,
|
||||
attrs: &mut Vec<Attribute>,
|
||||
sig: &Signature,
|
||||
ident: &Ident,
|
||||
) -> Result<(), Diagnostic> {
|
||||
let mut attr_idxs = Vec::new();
|
||||
for (i, meta) in attrs
|
||||
@@ -71,12 +86,17 @@ impl Module {
|
||||
Some(name) => name,
|
||||
None => continue,
|
||||
};
|
||||
if name == "pyfunction" {
|
||||
self.add_item(Self::extract_function(sig, meta)?, meta_span)?;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
attr_idxs.push(i);
|
||||
let item = match name.to_string().as_str() {
|
||||
"pyfunction" => {
|
||||
attr_idxs.push(i);
|
||||
Self::extract_function(ident, meta)?
|
||||
}
|
||||
"pyclass" => Self::extract_class(ident, meta)?,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
self.add_item(item, meta_span)?;
|
||||
}
|
||||
let mut i = 0;
|
||||
let mut attr_idxs = &*attr_idxs;
|
||||
@@ -95,33 +115,37 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemSig<'a> {
|
||||
attrs: &'a mut Vec<Attribute>,
|
||||
sig: &'a Signature,
|
||||
}
|
||||
|
||||
fn extract_module_items(mut items: Vec<ItemSig>) -> Result<TokenStream2, Diagnostic> {
|
||||
fn extract_module_items(mut items: Vec<ItemIdent>) -> Result<TokenStream2, Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
let mut class = Module::default();
|
||||
let mut module = Module::default();
|
||||
|
||||
for item in items.iter_mut() {
|
||||
push_diag_result!(
|
||||
diagnostics,
|
||||
class.extract_item_from_syn(&mut item.attrs, item.sig),
|
||||
module.extract_item_from_syn(&mut item.attrs, item.ident),
|
||||
);
|
||||
}
|
||||
|
||||
let functions = class.items.into_iter().map(|item| match item {
|
||||
let functions = module.items.into_iter().map(|item| match item {
|
||||
ModuleItem::Function {
|
||||
item_ident,
|
||||
py_name,
|
||||
} => {
|
||||
let new_func = quote_spanned!(item_ident.span()=> .new_function(#item_ident));
|
||||
let new_func = quote_spanned!(item_ident.span() => .new_function(#item_ident));
|
||||
quote! {
|
||||
vm.__module_set_attr(&module, #py_name, vm.ctx#new_func).unwrap();
|
||||
}
|
||||
}
|
||||
ModuleItem::Class {
|
||||
item_ident,
|
||||
py_name,
|
||||
} => {
|
||||
let new_class = quote_spanned!(item_ident.span() => #item_ident::make_class(&vm.ctx));
|
||||
quote! {
|
||||
vm.__module_set_attr(&module, #py_name, #new_class).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Diagnostic::from_vec(diagnostics)?;
|
||||
@@ -140,12 +164,16 @@ pub fn impl_pymodule(attr: AttributeArgs, item: Item) -> Result<TokenStream2, Di
|
||||
let items = content
|
||||
.iter_mut()
|
||||
.filter_map(|item| match item {
|
||||
syn::Item::Fn(syn::ItemFn {
|
||||
Item::Fn(syn::ItemFn { attrs, sig, .. }) => Some(ItemIdent {
|
||||
attrs,
|
||||
vis: _vis,
|
||||
sig,
|
||||
..
|
||||
}) => Some(ItemSig { attrs, sig }),
|
||||
ident: &sig.ident,
|
||||
}),
|
||||
Item::Struct(syn::ItemStruct { attrs, ident, .. }) => {
|
||||
Some(ItemIdent { attrs, ident })
|
||||
}
|
||||
Item::Enum(syn::ItemEnum { attrs, ident, .. }) => {
|
||||
Some(ItemIdent { attrs, ident })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
+19
-26
@@ -1,6 +1,6 @@
|
||||
use super::Diagnostic;
|
||||
use std::collections::HashMap;
|
||||
use syn::{AttributeArgs, Ident, Lit, Meta, NestedMeta, Path, Signature};
|
||||
use syn::{Attribute, AttributeArgs, Ident, Lit, Meta, NestedMeta, Path};
|
||||
|
||||
pub fn path_eq(path: &Path, s: &str) -> bool {
|
||||
path.get_ident().map_or(false, |id| id == s)
|
||||
@@ -40,8 +40,13 @@ pub fn strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemIdent<'a> {
|
||||
pub attrs: &'a mut Vec<Attribute>,
|
||||
pub ident: &'a Ident,
|
||||
}
|
||||
|
||||
pub struct ItemMeta<'a> {
|
||||
sig: &'a Signature,
|
||||
ident: &'a Ident,
|
||||
parent_type: &'static str,
|
||||
meta: HashMap<String, Option<Lit>>,
|
||||
}
|
||||
@@ -53,12 +58,12 @@ impl<'a> ItemMeta<'a> {
|
||||
|
||||
pub fn from_nested_meta(
|
||||
parent_type: &'static str,
|
||||
sig: &'a Signature,
|
||||
ident: &'a Ident,
|
||||
nested_meta: &[NestedMeta],
|
||||
names: &[&'static str],
|
||||
) -> Result<Self, Diagnostic> {
|
||||
let mut extracted = Self {
|
||||
sig,
|
||||
ident,
|
||||
parent_type,
|
||||
meta: HashMap::new(),
|
||||
};
|
||||
@@ -66,18 +71,13 @@ impl<'a> ItemMeta<'a> {
|
||||
let validate_name = |name: &str, extracted: &Self| -> Result<(), Diagnostic> {
|
||||
if names.contains(&name) {
|
||||
if extracted.meta.contains_key(name) {
|
||||
bail_span!(
|
||||
&sig.ident,
|
||||
"#[{}] must have only one '{}'",
|
||||
parent_type,
|
||||
name
|
||||
);
|
||||
bail_span!(ident, "#[{}] must have only one '{}'", parent_type, name);
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
bail_span!(
|
||||
&sig.ident,
|
||||
ident,
|
||||
"#[{}({})] is not one of allowed attributes {}",
|
||||
parent_type,
|
||||
name,
|
||||
@@ -123,7 +123,7 @@ impl<'a> ItemMeta<'a> {
|
||||
Some(s.value())
|
||||
} else {
|
||||
bail_span!(
|
||||
&self.sig.ident,
|
||||
&self.ident,
|
||||
"#[{}({} = ...)] must be a string",
|
||||
self.parent_type,
|
||||
key
|
||||
@@ -132,7 +132,7 @@ impl<'a> ItemMeta<'a> {
|
||||
}
|
||||
Some(None) => {
|
||||
bail_span!(
|
||||
&self.sig.ident,
|
||||
&self.ident,
|
||||
"#[{}({} = ...)] is expected",
|
||||
self.parent_type,
|
||||
key,
|
||||
@@ -145,12 +145,7 @@ impl<'a> ItemMeta<'a> {
|
||||
fn _bool(&self, key: &str) -> Result<bool, Diagnostic> {
|
||||
Ok(match self.meta.get(key) {
|
||||
Some(Some(_)) => {
|
||||
bail_span!(
|
||||
&self.sig.ident,
|
||||
"#[{}({})] is expected",
|
||||
self.parent_type,
|
||||
key,
|
||||
);
|
||||
bail_span!(&self.ident, "#[{}({})] is expected", self.parent_type, key,);
|
||||
}
|
||||
Some(None) => true,
|
||||
None => false,
|
||||
@@ -158,9 +153,7 @@ impl<'a> ItemMeta<'a> {
|
||||
}
|
||||
|
||||
pub fn simple_name(&self) -> Result<String, Diagnostic> {
|
||||
Ok(self
|
||||
._str("name")?
|
||||
.unwrap_or_else(|| self.sig.ident.to_string()))
|
||||
Ok(self._str("name")?.unwrap_or_else(|| self.ident.to_string()))
|
||||
}
|
||||
|
||||
pub fn method_name(&self) -> Result<String, Diagnostic> {
|
||||
@@ -169,7 +162,7 @@ impl<'a> ItemMeta<'a> {
|
||||
Ok(if let Some(name) = name {
|
||||
name
|
||||
} else {
|
||||
let name = self.sig.ident.to_string();
|
||||
let name = self.ident.to_string();
|
||||
if magic {
|
||||
format!("__{}__", name)
|
||||
} else {
|
||||
@@ -186,12 +179,12 @@ impl<'a> ItemMeta<'a> {
|
||||
Ok(if let Some(name) = name {
|
||||
name
|
||||
} else {
|
||||
let sig_name = self.sig.ident.to_string();
|
||||
let sig_name = self.ident.to_string();
|
||||
let name = if setter {
|
||||
if let Some(name) = strip_prefix(&sig_name, "set_") {
|
||||
if name.is_empty() {
|
||||
bail_span!(
|
||||
&self.sig.ident,
|
||||
&self.ident,
|
||||
"A #[{}(setter)] fn with a set_* name must \
|
||||
have something after \"set_\"",
|
||||
self.parent_type
|
||||
@@ -200,7 +193,7 @@ impl<'a> ItemMeta<'a> {
|
||||
name.to_string()
|
||||
} else {
|
||||
bail_span!(
|
||||
&self.sig.ident,
|
||||
&self.ident,
|
||||
"A #[{}(setter)] fn must either have a `name` \
|
||||
parameter or a fn name along the lines of \"set_*\"",
|
||||
self.parent_type
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* https://docs.rs/byteorder/1.2.6/byteorder/
|
||||
*/
|
||||
|
||||
use crate::pyobject::{PyClassImpl, PyObjectRef};
|
||||
use crate::pyobject::PyObjectRef;
|
||||
use crate::VirtualMachine;
|
||||
|
||||
#[pymodule]
|
||||
@@ -26,7 +26,9 @@ mod _struct {
|
||||
objbool::IntoPyBool, objbytes::PyBytesRef, objstr::PyString, objstr::PyStringRef,
|
||||
objtuple::PyTuple, objtype::PyClassRef,
|
||||
};
|
||||
use crate::pyobject::{Either, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject};
|
||||
use crate::pyobject::{
|
||||
Either, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
|
||||
};
|
||||
use crate::VirtualMachine;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -663,7 +665,7 @@ mod _struct {
|
||||
|
||||
#[pyclass(name = "Struct")]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PyStruct {
|
||||
struct PyStruct {
|
||||
spec: FormatSpec,
|
||||
fmt_str: PyStringRef,
|
||||
}
|
||||
@@ -695,6 +697,7 @@ mod _struct {
|
||||
fn format(&self) -> PyStringRef {
|
||||
self.fmt_str.clone()
|
||||
}
|
||||
|
||||
#[pyproperty]
|
||||
fn size(&self) -> usize {
|
||||
self.spec.size()
|
||||
@@ -730,7 +733,6 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let module = _struct::make_module(vm);
|
||||
extend_module!(vm, module, {
|
||||
"error" => struct_error,
|
||||
"Struct" => _struct::PyStruct::make_class(ctx),
|
||||
});
|
||||
module
|
||||
}
|
||||
Reference in new issue
Block a user