mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-30 21:01:10 +00:00
Implement record pattern for parameters
This commit is contained in:
parent
388010cc27
commit
a4c6009ec6
9 changed files with 170 additions and 66 deletions
|
@ -29,6 +29,8 @@ use erg_parser::ast::DefKind;
|
|||
use CommonOpcode::*;
|
||||
|
||||
use erg_parser::ast::{NonDefaultParamSignature, ParamPattern, VarName};
|
||||
use erg_parser::token::DOT;
|
||||
use erg_parser::token::EQUAL;
|
||||
use erg_parser::token::{Token, TokenKind};
|
||||
|
||||
use crate::compile::{AccessKind, Name, StoreLoadKind};
|
||||
|
@ -53,7 +55,6 @@ fn fake_method_to_func(class: &str, name: &str) -> Option<&'static str> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This method obviously does not scale, so in the future all Python APIs will be replaced by declarations in d.er, and renaming will be done in `HIRDesugarer`.
|
||||
fn escape_name(ident: Identifier) -> Str {
|
||||
let vis = ident.vis();
|
||||
if let Some(py_name) = ident.vi.py_name {
|
||||
|
@ -801,7 +802,13 @@ impl PyCodeGenerator {
|
|||
}
|
||||
self.emit_load_name_instr(ident);
|
||||
}
|
||||
Accessor::Attr(a) => {
|
||||
Accessor::Attr(mut a) => {
|
||||
// Python's namedtuple, a representation of Record, does not allow attribute names such as `::x`.
|
||||
// Since Erg does not allow the coexistence of private and public variables with the same name, there is no problem in this trick.
|
||||
let is_record = a.obj.ref_t().is_record();
|
||||
if is_record {
|
||||
a.ident.dot = Some(DOT);
|
||||
}
|
||||
self.emit_expr(*a.obj);
|
||||
self.emit_load_attr_instr(a.ident);
|
||||
}
|
||||
|
@ -2245,10 +2252,7 @@ impl PyCodeGenerator {
|
|||
log!(info "entered {}", fn_name!());
|
||||
let line = block.ln_begin().unwrap_or(0);
|
||||
for param in params {
|
||||
self.emit_store_instr(
|
||||
Identifier::public_with_line(Token::dummy(), param, line),
|
||||
Name,
|
||||
);
|
||||
self.emit_store_instr(Identifier::public_with_line(DOT, param, line), Name);
|
||||
}
|
||||
let init_stack_len = self.stack_len();
|
||||
for expr in block.into_iter() {
|
||||
|
@ -2264,10 +2268,7 @@ impl PyCodeGenerator {
|
|||
log!(info "entered {}", fn_name!());
|
||||
let line = block.ln_begin().unwrap_or(0);
|
||||
for param in params {
|
||||
self.emit_store_instr(
|
||||
Identifier::public_with_line(Token::dummy(), param, line),
|
||||
Name,
|
||||
);
|
||||
self.emit_store_instr(Identifier::public_with_line(DOT, param, line), Name);
|
||||
}
|
||||
let init_stack_len = self.stack_len();
|
||||
for expr in block.into_iter() {
|
||||
|
@ -2349,7 +2350,7 @@ impl PyCodeGenerator {
|
|||
log!(info "entered {}", fn_name!());
|
||||
let line = sig.ln_begin().unwrap();
|
||||
let class_name = sig.ident().inspect();
|
||||
let ident = Identifier::public_with_line(Token::dummy(), Str::ever("__init__"), line);
|
||||
let ident = Identifier::public_with_line(DOT, Str::ever("__init__"), line);
|
||||
let param_name = fresh_varname();
|
||||
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
|
||||
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
|
||||
|
@ -2369,14 +2370,14 @@ impl PyCodeGenerator {
|
|||
let obj =
|
||||
Expr::Accessor(Accessor::private_with_line(Str::from(¶m_name), line));
|
||||
let expr = obj.attr_expr(Identifier::bare(
|
||||
Some(Token::dummy()),
|
||||
Some(DOT),
|
||||
VarName::from_str(field.symbol.clone()),
|
||||
));
|
||||
let obj = Expr::Accessor(Accessor::private_with_line(Str::ever("self"), line));
|
||||
let dot = if field.vis.is_private() {
|
||||
None
|
||||
} else {
|
||||
Some(Token::dummy())
|
||||
Some(DOT)
|
||||
};
|
||||
let attr = obj.attr(Identifier::bare(
|
||||
dot,
|
||||
|
@ -2391,7 +2392,7 @@ impl PyCodeGenerator {
|
|||
other => todo!("{other}"),
|
||||
}
|
||||
let block = Block::new(attrs);
|
||||
let body = DefBody::new(Token::dummy(), block, DefId(0));
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
self.emit_subr_def(Some(class_name), subr_sig, body);
|
||||
}
|
||||
|
||||
|
@ -2404,7 +2405,7 @@ impl PyCodeGenerator {
|
|||
log!(info "entered {}", fn_name!());
|
||||
let class_ident = sig.ident();
|
||||
let line = sig.ln_begin().unwrap();
|
||||
let ident = Identifier::public_with_line(Token::dummy(), Str::ever("new"), line);
|
||||
let ident = Identifier::public_with_line(DOT, Str::ever("new"), line);
|
||||
let param_name = fresh_varname();
|
||||
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
|
||||
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
|
||||
|
@ -2420,7 +2421,7 @@ impl PyCodeGenerator {
|
|||
let class_new = class.attr_expr(new_ident);
|
||||
let call = class_new.call_expr(Args::new(vec![arg], None, vec![], None));
|
||||
let block = Block::new(vec![call]);
|
||||
let body = DefBody::new(Token::dummy(), block, DefId(0));
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
self.emit_subr_def(Some(class_ident.inspect()), sig, body);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,26 @@ impl Context {
|
|||
fv.generalize();
|
||||
TyParam::FreeVar(fv)
|
||||
}
|
||||
TyParam::Array(tps) => TyParam::Array(
|
||||
tps.into_iter()
|
||||
.map(|tp| self.generalize_tp(tp, variance))
|
||||
.collect(),
|
||||
),
|
||||
TyParam::Tuple(tps) => TyParam::Tuple(
|
||||
tps.into_iter()
|
||||
.map(|tp| self.generalize_tp(tp, variance))
|
||||
.collect(),
|
||||
),
|
||||
TyParam::Dict(tps) => TyParam::Dict(
|
||||
tps.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
self.generalize_tp(k, variance),
|
||||
self.generalize_tp(v, variance),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
TyParam::FreeVar(_) => free,
|
||||
other if other.has_no_unbound_var() => other,
|
||||
other => todo!("{other}"),
|
||||
|
@ -980,6 +1000,27 @@ impl Context {
|
|||
self.sub_unify(l, &r, loc, None)?;
|
||||
Ok(())
|
||||
}
|
||||
(TyParam::Array(ls), TyParam::Array(rs)) | (TyParam::Tuple(ls), TyParam::Tuple(rs)) => {
|
||||
for (l, r) in ls.iter().zip(rs.iter()) {
|
||||
self.sub_unify_tp(l, r, _variance, loc, allow_divergence)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(TyParam::Dict(ls), TyParam::Dict(rs)) => {
|
||||
for (lk, lv) in ls.iter() {
|
||||
if let Some(rv) = rs.get(lk) {
|
||||
self.sub_unify_tp(lv, rv, _variance, loc, allow_divergence)?;
|
||||
} else {
|
||||
// TODO:
|
||||
return Err(TyCheckErrors::from(TyCheckError::unreachable(
|
||||
self.cfg.input.clone(),
|
||||
fn_name!(),
|
||||
line!(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(l, r) => panic!("type-parameter unification failed:\nl:{l}\nr: {r}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use erg_common::{
|
|||
use erg_parser::ast::{
|
||||
fmt_lines, DefId, DefKind, NonDefaultParamSignature, OperationKind, TypeSpec, VarName,
|
||||
};
|
||||
use erg_parser::token::{Token, TokenKind};
|
||||
use erg_parser::token::{Token, TokenKind, DOT};
|
||||
|
||||
use crate::ty::constructors::{array_t, dict_t, set_t, tuple_t};
|
||||
use crate::ty::typaram::TyParam;
|
||||
|
@ -506,7 +506,7 @@ impl Accessor {
|
|||
}
|
||||
|
||||
pub fn public_with_line(name: Str, line: usize) -> Self {
|
||||
Self::Ident(Identifier::public_with_line(Token::dummy(), name, line))
|
||||
Self::Ident(Identifier::public_with_line(DOT, name, line))
|
||||
}
|
||||
|
||||
pub const fn private(name: Token, vi: VarInfo) -> Self {
|
||||
|
@ -514,12 +514,7 @@ impl Accessor {
|
|||
}
|
||||
|
||||
pub fn public(name: Token, vi: VarInfo) -> Self {
|
||||
Self::Ident(Identifier::new(
|
||||
Some(Token::dummy()),
|
||||
VarName::new(name),
|
||||
None,
|
||||
vi,
|
||||
))
|
||||
Self::Ident(Identifier::new(Some(DOT), VarName::new(name), None, vi))
|
||||
}
|
||||
|
||||
pub fn attr(obj: Expr, ident: Identifier) -> Self {
|
||||
|
|
|
@ -8,7 +8,7 @@ use erg_common::Str;
|
|||
use erg_common::{enum_unwrap, log};
|
||||
|
||||
use erg_parser::ast::{DefId, OperationKind};
|
||||
use erg_parser::token::{Token, TokenKind};
|
||||
use erg_parser::token::{Token, TokenKind, DOT, EQUAL};
|
||||
|
||||
use crate::ty::free::fresh_varname;
|
||||
use crate::ty::typaram::TyParam;
|
||||
|
@ -314,7 +314,7 @@ impl<'a> Linker<'a> {
|
|||
Identifier::private_with_line(Str::from(fresh_varname()), expr.ln_begin().unwrap());
|
||||
let mod_def = Expr::Def(Def::new(
|
||||
Signature::Var(VarSignature::new(tmp.clone())),
|
||||
DefBody::new(Token::dummy(), block, DefId(0)),
|
||||
DefBody::new(EQUAL, block, DefId(0)),
|
||||
));
|
||||
let module = Expr::Accessor(Accessor::Ident(tmp));
|
||||
let __dict__ = Identifier::public("__dict__");
|
||||
|
@ -382,7 +382,7 @@ impl<'a> Linker<'a> {
|
|||
for attr in comps {
|
||||
*expr = mem::replace(expr, Expr::Code(Block::empty())).attr_expr(
|
||||
Identifier::public_with_line(
|
||||
Token::dummy(),
|
||||
DOT,
|
||||
Str::rc(attr.as_os_str().to_str().unwrap()),
|
||||
line,
|
||||
),
|
||||
|
|
|
@ -1600,6 +1600,15 @@ impl Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_record(&self) -> bool {
|
||||
match self {
|
||||
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_record(),
|
||||
Self::Record(_) => true,
|
||||
Self::Refinement(refine) => refine.t.is_record(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_module(&self) -> bool {
|
||||
match self {
|
||||
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_module(),
|
||||
|
|
|
@ -14,12 +14,12 @@ use crate::ast::{
|
|||
Accessor, Args, Array, ArrayComprehension, ArrayTypeSpec, ArrayWithLength, BinOp, Block, Call,
|
||||
ClassAttr, ClassAttrs, ConstExpr, DataPack, Def, DefBody, DefId, Dict, Expr, Identifier,
|
||||
KeyValue, KwArg, Lambda, LambdaSignature, Literal, Methods, Module, NonDefaultParamSignature,
|
||||
NormalArray, NormalDict, NormalRecord, NormalSet, NormalTuple, ParamPattern, Params, PosArg,
|
||||
Record, RecordAttrs, Set as astSet, SetWithLength, ShortenedRecord, Signature, SubrSignature,
|
||||
Tuple, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, UnaryOp, VarName, VarPattern, VarRecordAttr,
|
||||
VarSignature,
|
||||
NormalArray, NormalDict, NormalRecord, NormalSet, NormalTuple, ParamPattern, ParamRecordAttr,
|
||||
Params, PosArg, Record, RecordAttrs, Set as astSet, SetWithLength, ShortenedRecord, Signature,
|
||||
SubrSignature, Tuple, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, UnaryOp, VarName, VarPattern,
|
||||
VarRecordAttr, VarSignature,
|
||||
};
|
||||
use crate::token::{Token, TokenKind};
|
||||
use crate::token::{Token, TokenKind, COLON, DOT};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum BufIndex<'i> {
|
||||
|
@ -635,10 +635,7 @@ impl Desugarer {
|
|||
l.ln_begin().unwrap(),
|
||||
l.col_begin().unwrap(),
|
||||
));
|
||||
param.t_spec = Some(TypeSpecWithOp::new(
|
||||
Token::dummy(),
|
||||
TypeSpec::enum_t_spec(vec![lit]),
|
||||
));
|
||||
param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::enum_t_spec(vec![lit])));
|
||||
}
|
||||
ParamPattern::Tuple(tup) => {
|
||||
let (buf_name, buf_param) = self.gen_buf_nd_param(line);
|
||||
|
@ -661,7 +658,7 @@ impl Desugarer {
|
|||
);
|
||||
}
|
||||
if param.t_spec.is_none() {
|
||||
param.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Tuple(tys)));
|
||||
param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Tuple(tys)));
|
||||
}
|
||||
param.pat = buf_param;
|
||||
}
|
||||
|
@ -681,27 +678,42 @@ impl Desugarer {
|
|||
let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0));
|
||||
let infer = Token::new(TokenKind::Try, "?", line, 0);
|
||||
let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len));
|
||||
param.t_spec =
|
||||
Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Array(t_spec)));
|
||||
param.t_spec = Some(TypeSpecWithOp::new(
|
||||
Token::dummy(TokenKind::Colon, ":"),
|
||||
TypeSpec::Array(t_spec),
|
||||
));
|
||||
}
|
||||
param.pat = buf_param;
|
||||
}
|
||||
/*
|
||||
VarPattern::Record(rec) => {
|
||||
let (buf_name, buf_sig) =
|
||||
self.gen_buf_name_and_sig(v.ln_begin().unwrap(), v.t_spec);
|
||||
let buf_def = Def::new(buf_sig, body);
|
||||
new.push(Expr::Def(buf_def));
|
||||
for VarRecordAttr { lhs, rhs } in rec.attrs.iter() {
|
||||
self.desugar_nested_var_pattern(
|
||||
&mut new,
|
||||
ParamPattern::Record(rec) => {
|
||||
let (buf_name, buf_param) = self.gen_buf_nd_param(line);
|
||||
for ParamRecordAttr { lhs, rhs } in rec.elems.iter_mut() {
|
||||
insertion_idx = self.desugar_nested_param_pattern(
|
||||
body,
|
||||
rhs,
|
||||
&buf_name,
|
||||
BufIndex::Record(lhs),
|
||||
insertion_idx,
|
||||
);
|
||||
}
|
||||
if param.t_spec.is_none() {
|
||||
let mut tys = vec![];
|
||||
for ParamRecordAttr { lhs, rhs } in rec.elems.iter() {
|
||||
let infer = Token::new(TokenKind::Try, "?", line, 0);
|
||||
tys.push((
|
||||
lhs.clone(),
|
||||
rhs.t_spec
|
||||
.as_ref()
|
||||
.map(|ts| ts.t_spec.clone())
|
||||
.unwrap_or(TypeSpec::Infer(infer))
|
||||
.clone(),
|
||||
));
|
||||
}
|
||||
VarPattern::DataPack(pack) => {
|
||||
param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Record(tys)));
|
||||
}
|
||||
param.pat = buf_param;
|
||||
}
|
||||
/*ParamPattern::DataPack(pack) => {
|
||||
let (buf_name, buf_sig) = self.gen_buf_name_and_sig(
|
||||
v.ln_begin().unwrap(),
|
||||
Some(pack.class.clone()), // TODO: これだとvの型指定の意味がなくなる
|
||||
|
@ -781,7 +793,7 @@ impl Desugarer {
|
|||
);
|
||||
}
|
||||
if sig.t_spec.is_none() {
|
||||
sig.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Tuple(tys)));
|
||||
sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Tuple(tys)));
|
||||
}
|
||||
sig.pat = buf_sig;
|
||||
insertion_idx
|
||||
|
@ -813,24 +825,50 @@ impl Desugarer {
|
|||
let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0));
|
||||
let infer = Token::new(TokenKind::Try, "?", line, 0);
|
||||
let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len));
|
||||
sig.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Array(t_spec)));
|
||||
sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Array(t_spec)));
|
||||
}
|
||||
sig.pat = buf_sig;
|
||||
insertion_idx
|
||||
}
|
||||
/*VarPattern::Record(rec) => {
|
||||
let (buf_name, buf_sig) = self.gen_buf_name_and_sig(sig.ln_begin().unwrap(), None);
|
||||
let buf_def = Def::new(buf_sig, body);
|
||||
new_module.push(Expr::Def(buf_def));
|
||||
for VarRecordAttr { lhs, rhs } in rec.attrs.iter() {
|
||||
self.desugar_nested_var_pattern(
|
||||
new_module,
|
||||
ParamPattern::Record(rec) => {
|
||||
let (buf_name, buf_sig) = self.gen_buf_nd_param(line);
|
||||
new_sub_body.block.insert(
|
||||
insertion_idx,
|
||||
Expr::Def(Def::new(
|
||||
Signature::Var(VarSignature::new(
|
||||
VarPattern::Ident(Identifier::private(Str::from(&buf_name))),
|
||||
sig.t_spec.as_ref().map(|ts| ts.t_spec.clone()),
|
||||
)),
|
||||
body,
|
||||
)),
|
||||
);
|
||||
insertion_idx += 1;
|
||||
let mut tys = vec![];
|
||||
for ParamRecordAttr { lhs, rhs } in rec.elems.iter_mut() {
|
||||
insertion_idx = self.desugar_nested_param_pattern(
|
||||
new_sub_body,
|
||||
rhs,
|
||||
&buf_name,
|
||||
BufIndex::Record(lhs),
|
||||
insertion_idx,
|
||||
);
|
||||
let infer = Token::new(TokenKind::Try, "?", line, 0);
|
||||
tys.push((
|
||||
lhs.clone(),
|
||||
rhs.t_spec
|
||||
.as_ref()
|
||||
.map(|ts| ts.t_spec.clone())
|
||||
.unwrap_or(TypeSpec::Infer(infer))
|
||||
.clone(),
|
||||
));
|
||||
}
|
||||
if sig.t_spec.is_none() {
|
||||
sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Record(tys)));
|
||||
}
|
||||
sig.pat = buf_sig;
|
||||
insertion_idx
|
||||
}
|
||||
/*
|
||||
VarPattern::DataPack(pack) => {
|
||||
let (buf_name, buf_sig) =
|
||||
self.gen_buf_name_and_sig(sig.ln_begin().unwrap(), Some(pack.class.clone()));
|
||||
|
@ -844,7 +882,8 @@ impl Desugarer {
|
|||
BufIndex::Record(lhs),
|
||||
);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
*/
|
||||
ParamPattern::VarName(name) => {
|
||||
let v = VarSignature::new(
|
||||
VarPattern::Ident(Identifier::new(None, name.clone())),
|
||||
|
@ -894,7 +933,7 @@ impl Desugarer {
|
|||
let call = Call::new(
|
||||
Self::rec_desugar_acc(*subscr.obj),
|
||||
Some(Identifier::public_with_line(
|
||||
Token::dummy(),
|
||||
DOT,
|
||||
Str::ever("__getitem__"),
|
||||
line,
|
||||
)),
|
||||
|
@ -909,7 +948,7 @@ impl Desugarer {
|
|||
let call = Call::new(
|
||||
Self::rec_desugar_acc(*tattr.obj),
|
||||
Some(Identifier::public_with_line(
|
||||
Token::dummy(),
|
||||
DOT,
|
||||
Str::ever("__Tuple_getitem__"),
|
||||
line,
|
||||
)),
|
||||
|
|
|
@ -311,6 +311,10 @@ pub struct Token {
|
|||
pub col_begin: usize,
|
||||
}
|
||||
|
||||
pub const COLON: Token = Token::dummy(TokenKind::Colon, ":");
|
||||
pub const DOT: Token = Token::dummy(TokenKind::Dot, ".");
|
||||
pub const EQUAL: Token = Token::dummy(TokenKind::Equal, "=");
|
||||
|
||||
impl fmt::Debug for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Token")
|
||||
|
@ -364,11 +368,17 @@ impl Locational for Token {
|
|||
}
|
||||
|
||||
impl Token {
|
||||
#[inline]
|
||||
pub fn dummy() -> Self {
|
||||
Token {
|
||||
pub const DUMMY: Token = Token {
|
||||
kind: TokenKind::Illegal,
|
||||
content: "DUMMY".into(),
|
||||
content: Str::ever("DUMMY"),
|
||||
lineno: 1,
|
||||
col_begin: 0,
|
||||
};
|
||||
|
||||
pub const fn dummy(kind: TokenKind, content: &'static str) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
content: Str::ever(content),
|
||||
lineno: 1,
|
||||
col_begin: 0,
|
||||
}
|
||||
|
|
4
tests/pattern.er
Normal file
4
tests/pattern.er
Normal file
|
@ -0,0 +1,4 @@
|
|||
f {x; y}, [_], (_, _) = x + y
|
||||
|
||||
x = f {x = 1; y = 2}, [3], (4, 5)
|
||||
assert x == 3
|
|
@ -74,6 +74,11 @@ fn exec_move_check() -> Result<(), ()> {
|
|||
expect_failure("examples/move_check.er", 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_pattern() -> Result<(), ()> {
|
||||
expect_success("tests/pattern.er")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_pyimport() -> Result<(), ()> {
|
||||
if cfg!(unix) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue