mirror of
https://github.com/erg-lang/erg.git
synced 2025-10-01 21:21:10 +00:00
WIP: impl Trait
This commit is contained in:
parent
346d00fcd8
commit
aa527dcbc8
11 changed files with 227 additions and 51 deletions
|
@ -16,10 +16,12 @@ use erg_parser::token::{Token, TokenKind};
|
|||
use erg_type::constructors::{enum_t, mono, mono_proj, poly, ref_, ref_mut, refinement, subr_t};
|
||||
use erg_type::typaram::{OpKind, TyParam};
|
||||
use erg_type::value::ValueObj;
|
||||
use erg_type::{ConstSubr, HasType, Predicate, TyBound, Type, UserConstSubr, ValueArgs};
|
||||
use erg_type::{
|
||||
ConstSubr, HasType, ParamTy, Predicate, SubrKind, TyBound, Type, UserConstSubr, ValueArgs,
|
||||
};
|
||||
|
||||
use crate::context::instantiate::TyVarContext;
|
||||
use crate::context::{ClassDefType, Context};
|
||||
use crate::context::{ClassDefType, Context, RegistrationMode};
|
||||
use crate::error::{EvalError, EvalResult, TyCheckResult};
|
||||
|
||||
#[inline]
|
||||
|
@ -343,7 +345,7 @@ impl Context {
|
|||
fn eval_const_normal_record(&self, record: &NormalRecord) -> EvalResult<ValueObj> {
|
||||
let mut attrs = vec![];
|
||||
// HACK: should avoid cloning
|
||||
let mut record_ctx = Context::instant(Str::ever("<unnamed record>"), 2, self.clone());
|
||||
let mut record_ctx = Context::instant(Str::ever("<unnamed record>"), 0, self.clone());
|
||||
for attr in record.attrs.iter() {
|
||||
let name = attr.sig.ident().map(|i| i.inspect());
|
||||
let elem = record_ctx.eval_const_block(&attr.body.block, name)?;
|
||||
|
@ -360,11 +362,63 @@ impl Context {
|
|||
}
|
||||
|
||||
fn eval_const_lambda(&self, lambda: &Lambda) -> EvalResult<ValueObj> {
|
||||
let mut non_default_params = Vec::with_capacity(lambda.sig.params.non_defaults.len());
|
||||
for sig in lambda.sig.params.non_defaults.iter() {
|
||||
let t = self.instantiate_param_sig_t(sig, None, RegistrationMode::Normal)?;
|
||||
let pt = if let Some(name) = sig.inspect() {
|
||||
ParamTy::kw(name.clone(), t)
|
||||
} else {
|
||||
ParamTy::anonymous(t)
|
||||
};
|
||||
non_default_params.push(pt);
|
||||
}
|
||||
let var_params = if let Some(p) = lambda.sig.params.var_args.as_ref() {
|
||||
let t = self.instantiate_param_sig_t(p, None, RegistrationMode::Normal)?;
|
||||
let pt = if let Some(name) = p.inspect() {
|
||||
ParamTy::kw(name.clone(), t)
|
||||
} else {
|
||||
ParamTy::anonymous(t)
|
||||
};
|
||||
Some(pt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut default_params = Vec::with_capacity(lambda.sig.params.defaults.len());
|
||||
for sig in lambda.sig.params.defaults.iter() {
|
||||
let t = self.instantiate_param_sig_t(sig, None, RegistrationMode::Normal)?;
|
||||
let pt = if let Some(name) = sig.inspect() {
|
||||
ParamTy::kw(name.clone(), t)
|
||||
} else {
|
||||
ParamTy::anonymous(t)
|
||||
};
|
||||
default_params.push(pt);
|
||||
}
|
||||
// HACK: should avoid cloning
|
||||
let mut lambda_ctx = Context::instant(Str::ever("<lambda>"), 0, self.clone());
|
||||
let return_t = lambda_ctx.eval_const_block(&lambda.body, None)?;
|
||||
// FIXME: lambda: i: Int -> Int
|
||||
// => sig_t: (i: Type) -> Type
|
||||
// => as_type: (i: Int) -> Int
|
||||
let sig_t = subr_t(
|
||||
SubrKind::from(lambda.op.kind),
|
||||
non_default_params.clone(),
|
||||
var_params.clone(),
|
||||
default_params.clone(),
|
||||
enum_t(set![return_t.clone()]),
|
||||
);
|
||||
let as_type = subr_t(
|
||||
SubrKind::from(lambda.op.kind),
|
||||
non_default_params,
|
||||
var_params,
|
||||
default_params,
|
||||
return_t.as_type().ok_or_else(|| todo!())?.into_typ(),
|
||||
);
|
||||
let subr = ConstSubr::User(UserConstSubr::new(
|
||||
Str::ever("<lambda>"),
|
||||
lambda.sig.params.clone(),
|
||||
lambda.body.clone(),
|
||||
Type::Uninited,
|
||||
sig_t,
|
||||
Some(as_type),
|
||||
));
|
||||
Ok(ValueObj::Subr(subr))
|
||||
}
|
||||
|
|
|
@ -7,24 +7,8 @@ use erg_common::color::{RED, RESET};
|
|||
use erg_common::error::{ErrorCore, ErrorKind, Location};
|
||||
use erg_type::constructors::{and, mono};
|
||||
use erg_type::value::{EvalValueResult, TypeKind, TypeObj, ValueObj};
|
||||
use erg_type::Type;
|
||||
use erg_type::ValueArgs;
|
||||
|
||||
fn value_obj_to_t(value: ValueObj) -> TypeObj {
|
||||
match value {
|
||||
ValueObj::Type(t) => t,
|
||||
ValueObj::Record(rec) => TypeObj::Builtin(Type::Record(
|
||||
rec.into_iter()
|
||||
.map(|(k, v)| (k, value_obj_to_t(v).typ().clone()))
|
||||
.collect(),
|
||||
)),
|
||||
ValueObj::Subr(subr) => {
|
||||
todo!("{subr}")
|
||||
}
|
||||
other => todo!("{other}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Requirement: Type, Impl := Type -> ClassType
|
||||
pub fn class_func(mut args: ValueArgs, __name__: Option<Str>) -> EvalValueResult<ValueObj> {
|
||||
let require = args.remove_left_or_key("Requirement").ok_or_else(|| {
|
||||
|
@ -36,9 +20,9 @@ pub fn class_func(mut args: ValueArgs, __name__: Option<Str>) -> EvalValueResult
|
|||
None,
|
||||
)
|
||||
})?;
|
||||
let require = value_obj_to_t(require);
|
||||
let require = require.as_type().unwrap();
|
||||
let impls = args.remove_left_or_key("Impl");
|
||||
let impls = impls.map(value_obj_to_t);
|
||||
let impls = impls.map(|v| v.as_type().unwrap());
|
||||
let t = mono(__name__.unwrap_or(Str::ever("<Lambda>")));
|
||||
Ok(ValueObj::gen_t(TypeKind::Class, t, require, impls, None))
|
||||
}
|
||||
|
@ -54,11 +38,11 @@ pub fn inherit_func(mut args: ValueArgs, __name__: Option<Str>) -> EvalValueResu
|
|||
None,
|
||||
)
|
||||
})?;
|
||||
let sup = value_obj_to_t(sup);
|
||||
let sup = sup.as_type().unwrap();
|
||||
let impls = args.remove_left_or_key("Impl");
|
||||
let impls = impls.map(value_obj_to_t);
|
||||
let impls = impls.map(|v| v.as_type().unwrap());
|
||||
let additional = args.remove_left_or_key("Additional");
|
||||
let additional = additional.map(value_obj_to_t);
|
||||
let additional = additional.map(|v| v.as_type().unwrap());
|
||||
let t = mono(__name__.unwrap_or(Str::ever("<Lambda>")));
|
||||
Ok(ValueObj::gen_t(
|
||||
TypeKind::Subclass,
|
||||
|
@ -112,9 +96,9 @@ pub fn trait_func(mut args: ValueArgs, __name__: Option<Str>) -> EvalValueResult
|
|||
None,
|
||||
)
|
||||
})?;
|
||||
let require = value_obj_to_t(require);
|
||||
let require = require.as_type().unwrap();
|
||||
let impls = args.remove_left_or_key("Impl");
|
||||
let impls = impls.map(value_obj_to_t);
|
||||
let impls = impls.map(|v| v.as_type().unwrap());
|
||||
let t = mono(__name__.unwrap_or(Str::ever("<Lambda>")));
|
||||
Ok(ValueObj::gen_t(TypeKind::Trait, t, require, impls, None))
|
||||
}
|
||||
|
@ -130,11 +114,11 @@ pub fn subsume_func(mut args: ValueArgs, __name__: Option<Str>) -> EvalValueResu
|
|||
None,
|
||||
)
|
||||
})?;
|
||||
let sup = value_obj_to_t(sup);
|
||||
let sup = sup.as_type().unwrap();
|
||||
let impls = args.remove_left_or_key("Impl");
|
||||
let impls = impls.map(value_obj_to_t);
|
||||
let impls = impls.map(|v| v.as_type().unwrap());
|
||||
let additional = args.remove_left_or_key("Additional");
|
||||
let additional = additional.map(value_obj_to_t);
|
||||
let additional = additional.map(|v| v.as_type().unwrap());
|
||||
let t = mono(__name__.unwrap_or(Str::ever("<Lambda>")));
|
||||
Ok(ValueObj::gen_t(
|
||||
TypeKind::Subtrait,
|
||||
|
|
|
@ -1486,7 +1486,7 @@ impl Context {
|
|||
vec![param_t("Impl", Type)],
|
||||
Class,
|
||||
);
|
||||
let class = ConstSubr::Builtin(BuiltinConstSubr::new("Class", class_func, class_t));
|
||||
let class = ConstSubr::Builtin(BuiltinConstSubr::new("Class", class_func, class_t, None));
|
||||
self.register_builtin_const("Class", ValueObj::Subr(class));
|
||||
let inherit_t = func(
|
||||
vec![param_t("Super", Class)],
|
||||
|
@ -1494,7 +1494,12 @@ impl Context {
|
|||
vec![param_t("Impl", Type), param_t("Additional", Type)],
|
||||
Class,
|
||||
);
|
||||
let inherit = ConstSubr::Builtin(BuiltinConstSubr::new("Inherit", inherit_func, inherit_t));
|
||||
let inherit = ConstSubr::Builtin(BuiltinConstSubr::new(
|
||||
"Inherit",
|
||||
inherit_func,
|
||||
inherit_t,
|
||||
None,
|
||||
));
|
||||
self.register_builtin_const("Inherit", ValueObj::Subr(inherit));
|
||||
let trait_t = func(
|
||||
vec![param_t("Requirement", Type)],
|
||||
|
@ -1502,7 +1507,7 @@ impl Context {
|
|||
vec![param_t("Impl", Type)],
|
||||
Trait,
|
||||
);
|
||||
let trait_ = ConstSubr::Builtin(BuiltinConstSubr::new("Trait", trait_func, trait_t));
|
||||
let trait_ = ConstSubr::Builtin(BuiltinConstSubr::new("Trait", trait_func, trait_t, None));
|
||||
self.register_builtin_const("Trait", ValueObj::Subr(trait_));
|
||||
let subsume_t = func(
|
||||
vec![param_t("Super", Trait)],
|
||||
|
@ -1510,7 +1515,12 @@ impl Context {
|
|||
vec![param_t("Impl", Type), param_t("Additional", Type)],
|
||||
Trait,
|
||||
);
|
||||
let subsume = ConstSubr::Builtin(BuiltinConstSubr::new("Subsume", subsume_func, subsume_t));
|
||||
let subsume = ConstSubr::Builtin(BuiltinConstSubr::new(
|
||||
"Subsume",
|
||||
subsume_func,
|
||||
subsume_t,
|
||||
None,
|
||||
));
|
||||
self.register_builtin_const("Subsume", ValueObj::Subr(subsume));
|
||||
// decorators
|
||||
let inheritable_t = func1(Class, Class);
|
||||
|
@ -1518,6 +1528,7 @@ impl Context {
|
|||
"Inheritable",
|
||||
inheritable_func,
|
||||
inheritable_t,
|
||||
None,
|
||||
));
|
||||
self.register_builtin_const("Inheritable", ValueObj::Subr(inheritable));
|
||||
}
|
||||
|
|
|
@ -1259,6 +1259,7 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// FIXME: if trait, returns a freevar
|
||||
pub(crate) fn rec_get_self_t(&self) -> Option<Type> {
|
||||
if self.kind.is_method_def() || self.kind.is_type() {
|
||||
// TODO: poly type
|
||||
|
|
|
@ -628,11 +628,7 @@ impl Context {
|
|||
.collect();
|
||||
let return_t = self.instantiate_typespec(&subr.return_t, mode)?;
|
||||
Ok(subr_t(
|
||||
if subr.arrow.is(TokenKind::FuncArrow) {
|
||||
SubrKind::Func
|
||||
} else {
|
||||
SubrKind::Proc
|
||||
},
|
||||
SubrKind::from(subr.arrow.kind),
|
||||
non_defaults,
|
||||
var_args,
|
||||
defaults,
|
||||
|
|
|
@ -649,6 +649,28 @@ impl Context {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
TypeKind::Trait => {
|
||||
if gen.t.is_monomorphic() {
|
||||
let ctx = Self::mono_trait(gen.t.name(), self.level);
|
||||
self.register_gen_mono_type(ident, gen, ctx, Const);
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
TypeKind::Subtrait => {
|
||||
if gen.t.is_monomorphic() {
|
||||
let super_classes = vec![gen.require_or_sup.typ().clone()];
|
||||
// let super_traits = gen.impls.iter().map(|to| to.typ().clone()).collect();
|
||||
let mut ctx = Self::mono_trait(gen.t.name(), self.level);
|
||||
for sup in super_classes.into_iter() {
|
||||
let (_, sup_ctx) = self.get_nominal_type_ctx(&sup).unwrap();
|
||||
ctx.register_superclass(sup, sup_ctx);
|
||||
}
|
||||
self.register_gen_mono_type(ident, gen, ctx, Const);
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
other => todo!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1027,6 +1027,9 @@ impl Context {
|
|||
(Type::MonoProj { .. }, _) => todo!(),
|
||||
(_, Type::MonoProj { .. }) => todo!(),
|
||||
(Refinement(_), Refinement(_)) => todo!(),
|
||||
(Type::Subr(_) | Type::Record(_), Type) => Ok(()),
|
||||
// TODO Tuple2, ...
|
||||
(Type::Poly{ name, .. }, Type) if &name[..] == "Array" || &name[..] == "Tuple" => Ok(()),
|
||||
_ => todo!("{maybe_sub} can be a subtype of {maybe_sup}, but failed to semi-unify (or existential types are not supported)"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -562,7 +562,14 @@ impl ASTLowerer {
|
|||
name,
|
||||
));
|
||||
}
|
||||
self.ctx.grow(name, ContextKind::Instant, def.sig.vis())?;
|
||||
let kind = if def.is_class_def() {
|
||||
ContextKind::Class
|
||||
} else if def.is_trait_def() {
|
||||
ContextKind::Trait
|
||||
} else {
|
||||
ContextKind::Instant
|
||||
};
|
||||
self.ctx.grow(name, kind, def.sig.vis())?;
|
||||
let res = match def.sig {
|
||||
ast::Signature::Subr(sig) => self.lower_subr_def(sig, def.body),
|
||||
ast::Signature::Var(sig) => self.lower_var_def(sig, def.body),
|
||||
|
|
|
@ -225,6 +225,20 @@ impl Args {
|
|||
pub fn push_kw(&mut self, arg: KwArg) {
|
||||
self.kw_args.push(arg);
|
||||
}
|
||||
|
||||
pub fn get_left_or_key(&self, key: &str) -> Option<&Expr> {
|
||||
if !self.pos_args.is_empty() {
|
||||
self.pos_args.get(0).map(|a| &a.expr)
|
||||
} else {
|
||||
self.kw_args.iter().find_map(|a| {
|
||||
if &a.keyword.content[..] == key {
|
||||
Some(&a.expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -2862,6 +2876,34 @@ impl Def {
|
|||
pub const fn is_subr(&self) -> bool {
|
||||
matches!(&self.sig, Signature::Subr(_))
|
||||
}
|
||||
|
||||
pub fn is_class_def(&self) -> bool {
|
||||
match self.body.block.first().unwrap() {
|
||||
Expr::Call(call)
|
||||
if call.obj.get_name().map(|n| &n[..]) == Some("Class")
|
||||
|| call.obj.get_name().map(|n| &n[..]) == Some("Inherit") =>
|
||||
{
|
||||
true
|
||||
}
|
||||
Expr::Call(call) if call.obj.get_name().map(|n| &n[..]) == Some("Inheritable") => {
|
||||
if let Some(Expr::Call(inner)) = call.args.get_left_or_key("Class") {
|
||||
inner.obj.get_name().map(|n| &n[..]) == Some("Class")
|
||||
|| inner.obj.get_name().map(|n| &n[..]) == Some("Inherit")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_trait_def(&self) -> bool {
|
||||
matches!(
|
||||
self.body.block.first().unwrap(),
|
||||
Expr::Call(call) if call.obj.get_name().map(|n| &n[..]) == Some("Trait")
|
||||
|| call.obj.get_name().map(|n| &n[..]) == Some("Subsume")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// e.g.
|
||||
|
|
|
@ -20,6 +20,7 @@ use erg_common::vis::Field;
|
|||
use erg_common::{enum_unwrap, fmt_option, fmt_set_split_with, set, Str};
|
||||
|
||||
use erg_parser::ast::{Block, Params};
|
||||
use erg_parser::token::TokenKind;
|
||||
|
||||
use crate::constructors::{int_interval, mono, mono_q};
|
||||
use crate::free::{
|
||||
|
@ -135,16 +136,24 @@ pub struct UserConstSubr {
|
|||
name: Str,
|
||||
params: Params,
|
||||
block: Block,
|
||||
t: Type,
|
||||
sig_t: Type,
|
||||
as_type: Option<Type>,
|
||||
}
|
||||
|
||||
impl UserConstSubr {
|
||||
pub const fn new(name: Str, params: Params, block: Block, t: Type) -> Self {
|
||||
pub const fn new(
|
||||
name: Str,
|
||||
params: Params,
|
||||
block: Block,
|
||||
sig_t: Type,
|
||||
as_type: Option<Type>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
params,
|
||||
block,
|
||||
t,
|
||||
sig_t,
|
||||
as_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +182,8 @@ impl ValueArgs {
|
|||
pub struct BuiltinConstSubr {
|
||||
name: &'static str,
|
||||
subr: fn(ValueArgs, Option<Str>) -> EvalValueResult<ValueObj>,
|
||||
t: Type,
|
||||
sig_t: Type,
|
||||
as_type: Option<Type>,
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinConstSubr {
|
||||
|
@ -186,9 +196,15 @@ impl BuiltinConstSubr {
|
|||
pub const fn new(
|
||||
name: &'static str,
|
||||
subr: fn(ValueArgs, Option<Str>) -> EvalValueResult<ValueObj>,
|
||||
t: Type,
|
||||
sig_t: Type,
|
||||
as_type: Option<Type>,
|
||||
) -> Self {
|
||||
Self { name, subr, t }
|
||||
Self {
|
||||
name,
|
||||
subr,
|
||||
sig_t,
|
||||
as_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, args: ValueArgs, __name__: Option<Str>) -> EvalValueResult<ValueObj> {
|
||||
|
@ -214,10 +230,17 @@ impl fmt::Display for ConstSubr {
|
|||
}
|
||||
|
||||
impl ConstSubr {
|
||||
pub fn class(&self) -> Type {
|
||||
pub fn sig_t(&self) -> &Type {
|
||||
match self {
|
||||
ConstSubr::User(user) => user.t.clone(),
|
||||
ConstSubr::Builtin(builtin) => builtin.t.clone(),
|
||||
ConstSubr::User(user) => &user.sig_t,
|
||||
ConstSubr::Builtin(builtin) => &builtin.sig_t,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_type(&self) -> Option<&Type> {
|
||||
match self {
|
||||
ConstSubr::User(user) => user.as_type.as_ref(),
|
||||
ConstSubr::Builtin(builtin) => builtin.as_type.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -976,6 +999,16 @@ pub enum SubrKind {
|
|||
Proc,
|
||||
}
|
||||
|
||||
impl From<TokenKind> for SubrKind {
|
||||
fn from(op_kind: TokenKind) -> Self {
|
||||
match op_kind {
|
||||
TokenKind::FuncArrow => Self::Func,
|
||||
TokenKind::ProcArrow => Self::Proc,
|
||||
_ => panic!("invalid token kind for subr kind"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubrKind {
|
||||
pub const fn arrow(&self) -> Str {
|
||||
match self {
|
||||
|
|
|
@ -14,7 +14,7 @@ use erg_common::rccell::RcCell;
|
|||
use erg_common::serialize::*;
|
||||
use erg_common::set;
|
||||
use erg_common::vis::Field;
|
||||
use erg_common::{fmt_iter, impl_display_from_debug, switch_lang};
|
||||
use erg_common::{dict, fmt_iter, impl_display_from_debug, switch_lang};
|
||||
use erg_common::{RcArray, Str};
|
||||
|
||||
use crate::codeobj::CodeObj;
|
||||
|
@ -99,6 +99,13 @@ impl TypeObj {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_typ(self) -> Type {
|
||||
match self {
|
||||
TypeObj::Builtin(t) => t,
|
||||
TypeObj::Generated(t) => t.t,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_intersec(&self, other: &Type) -> bool {
|
||||
match self {
|
||||
TypeObj::Builtin(t) => t.contains_intersec(other),
|
||||
|
@ -482,7 +489,7 @@ impl ValueObj {
|
|||
Self::Record(rec) => {
|
||||
Type::Record(rec.iter().map(|(k, v)| (k.clone(), v.class())).collect())
|
||||
}
|
||||
Self::Subr(subr) => subr.class(),
|
||||
Self::Subr(subr) => subr.sig_t().clone(),
|
||||
Self::Type(t_obj) => match t_obj {
|
||||
// TODO: builtin
|
||||
TypeObj::Builtin(_t) => Type::Type,
|
||||
|
@ -771,6 +778,22 @@ impl ValueObj {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_type(&self) -> Option<TypeObj> {
|
||||
match self {
|
||||
Self::Type(t) => Some(t.clone()),
|
||||
Self::Record(rec) => {
|
||||
let mut attr_ts = dict! {};
|
||||
for (k, v) in rec.iter() {
|
||||
attr_ts.insert(k.clone(), v.as_type()?.typ().clone());
|
||||
}
|
||||
Some(TypeObj::Builtin(Type::Record(attr_ts)))
|
||||
}
|
||||
Self::Subr(subr) => Some(TypeObj::Builtin(subr.as_type().unwrap().clone())),
|
||||
Self::Array(_) | Self::Tuple(_) | Self::Dict(_) => todo!(),
|
||||
_other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod value_set {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue