diff --git a/bytecode/src/lib.rs b/bytecode/src/lib.rs index 26132dd7..3f8f20fa 100644 --- a/bytecode/src/lib.rs +++ b/bytecode/src/lib.rs @@ -254,7 +254,7 @@ pub fn disasm<'a>( || global_offset > off + len || prev .get(global_offset as usize) - .map_or(true, |&b| instr_from_byte(b).is_err()); + .is_none_or(|&b| instr_from_byte(b).is_err()); has_oob |= local_has_oob; let label = labels.get(&global_offset).unwrap(); if local_has_oob { diff --git a/lang/README.md b/lang/README.md index 4b04266b..0d3255a0 100644 --- a/lang/README.md +++ b/lang/README.md @@ -214,6 +214,14 @@ odher_pass := fn(t: Ty2): Ty2 { } ``` +#### tuples +```hb +main := fn(): uint { + tupl := .(1, 1) + return tupl[0] - tupl[1] +} +``` + #### struct_scopes ```hb $zr := 0 @@ -475,7 +483,7 @@ arbitrary text - `@embed()`: include relative file as an array of bytes - `@inline(, ...)`: equivalent to `(...)` but function is guaranteed to inline, compiler will otherwise never inline - `@len()`: reports a length of the type of indexing purposes or length ot a string constant -- `@kindof()`: gives an u8 integer describing the kind of type as an index to array `[Builtin, Struct, Enum, Union, Ptr, Slice, Opt, Func, Template, Global, Const, Module]` +- `@kindof()`: gives an u8 integer describing the kind of type as an index to array `[Builtin, Struct, Tuple, Enum, Union, Ptr, Slice, Opt, Func, Template, Global, Const, Module]` - `@Any()`: generic parameter based on inference, TBD: this will ake arguments in the future that restrict what is accepted - `@error(...)`: emit compiler error, if reachable, and use arguments to construct a message, can display strings and types - `@ChildOf()`: returns the child type of the ``, works for pointers and optionals (`@ChildOf(?u8) == u8`) diff --git a/lang/src/backend/hbvm.rs b/lang/src/backend/hbvm.rs index 19e40b9c..c3dc80c1 100644 --- a/lang/src/backend/hbvm.rs +++ b/lang/src/backend/hbvm.rs @@ -8,7 +8,7 @@ use { utils::{EntSlice, EntVec}, }, alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec}, - core::{assert_matches::debug_assert_matches, mem, ops::Range, usize}, + core::{assert_matches::debug_assert_matches, mem, ops::Range}, hbbytecode::{self as instrs, *}, reg::Reg, }; @@ -314,7 +314,7 @@ impl Backend for HbvmBackend { && self .jump_relocs .last() - .map_or(true, |&(r, _)| self.offsets[r as usize] as usize != self.code.len()) + .is_none_or(|&(r, _)| self.offsets[r as usize] as usize != self.code.len()) { self.code.truncate(self.code.len() - 5); self.ret_relocs.pop(); @@ -653,6 +653,7 @@ enum PLoc { WideReg(Reg, u16), Ref(Reg, u32), } + impl PLoc { fn reg(self) -> u8 { match self { diff --git a/lang/src/backend/hbvm/regalloc.rs b/lang/src/backend/hbvm/regalloc.rs index ac358c89..6140100b 100644 --- a/lang/src/backend/hbvm/regalloc.rs +++ b/lang/src/backend/hbvm/regalloc.rs @@ -804,6 +804,7 @@ impl Nodes { } } + #[expect(unused)] fn init_loc_of(&self, def: Nid, types: &Types) -> Reg { if self[def].kind == Kind::Arg { let mut parama = ParamAlloc(0..11); @@ -821,8 +822,9 @@ impl Nodes { 255 } + #[expect(unused)] fn use_reg_of(&self, def: Nid, usage: Nid) -> Reg { - if matches!(self[usage].kind, Kind::Return { .. }) {} + //if matches!(self[usage].kind, Kind::Return { .. }) {} 255 } @@ -852,7 +854,7 @@ impl<'a> Regalloc<'a> { Self { nodes: ctx, tys, res }.run_low(special_count); } - fn run_low(&mut self, special_count: usize) { + fn run_low(&mut self, #[expect(unused)] special_count: usize) { self.res.general_bundles.clear(); self.res.node_to_reg.clear(); #[cfg(debug_assertions)] diff --git a/lang/src/nodes.rs b/lang/src/nodes.rs index afd5be1b..4fca2e24 100644 --- a/lang/src/nodes.rs +++ b/lang/src/nodes.rs @@ -2089,7 +2089,7 @@ pub enum Kind { Call { unreachable: bool, func: ty::Func, - args: ty::Tuple, + args: ty::List, }, // [ctrl] Die, diff --git a/lang/src/son.rs b/lang/src/son.rs index e0023816..ab7021e5 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -15,9 +15,9 @@ use { }, ty::{ self, Arg, ArrayLen, CompState, ConstData, EnumData, EnumField, FTask, FuncData, - GlobalData, Loc, Module, Offset, OffsetIter, OptLayout, Sig, StringRef, StructData, - StructField, SymKey, TemplateData, Tuple, TypeBase, TypeIns, Types, UnionData, - UnionField, + GlobalData, List, Loc, Module, Offset, OffsetIter, OptLayout, Sig, StringRef, + StructData, StructField, SymKey, TemplateData, TupleData, TypeBase, TypeIns, Types, + UnionData, UnionField, }, utils::{BitSet, EntSlice, Vc}, Ident, @@ -657,7 +657,7 @@ impl<'a> Codegen<'a> { let fuc = self.tys.ins.funcs.push(FuncData { file, - sig: Sig { args: Tuple::empty(), ret }, + sig: Sig { args: List::empty(), ret }, ..Default::default() }); @@ -1274,10 +1274,37 @@ impl<'a> Codegen<'a> { Some(Value::ptr(self.offset(bs.id, offset)).ty(f)) } + ty::Kind::Tuple(t) => { + let Kind::CInt { value: idx } = self.ci.nodes[idx.id].kind else { + return self.error( + index.pos(), + "field index needs to be known at compile time", + ); + }; + + let Some((f, offset)) = OffsetIter::new(t, self.tys) + .into_iter(self.tys) + .nth(idx as _) + .map(|(&f, off)| (f, off)) + else { + return self.error( + index.pos(), + fa!( + "struct '{}' has only `{}' fields, \ + but index was '{}'", + self.ty_display(bs.ty), + self.tys.tuple_fields(t).len(), + idx + ), + ); + }; + + Some(Value::ptr(self.offset(bs.id, offset)).ty(f)) + } _ => self.error( base.pos(), fa!( - "cant index into '{}' which is not array nor slice", + "cant index into '{}' which is not array nor slice or tuple or struct", self.ty_display(bs.ty) ), ), @@ -1581,14 +1608,51 @@ impl<'a> Codegen<'a> { self.gen_call(func, args, true) } Expr::Tupl { pos, ty, fields, .. } => { - ctx.ty = ty + let ty = ty .map(|ty| self.ty(ty)) - .or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty))); - inference!(sty, ctx, self, pos, "struct or slice", ".(...)"); + .or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty))) + .map(ty::Id::expand); - match sty.expand() { - ty::Kind::Struct(s) => { - let mem = self.new_stack(pos, sty); + match ty { + None => { + let arg_base = self.tys.tmp.args.len(); + let mut values = Vec::with_capacity(fields.len()); + for field in fields { + let val = self.expr(field)?; + self.tys.tmp.args.push(val.ty); + self.ci.nodes.lock(val.id); + values.push(val); + } + + let Some(fields) = self.tys.pack_args(arg_base) else { + return self.error(pos, "this tuple exceeded the reasonable limit"); + }; + + let key = SymKey::Tuple(fields); + let ty::Kind::Tuple(tupl) = self + .tys + .syms + .get_or_insert(key, &mut self.tys.ins, |ins| { + ins.tuples.push(TupleData { fields, ..Default::default() }).into() + }) + .expand() + else { + unreachable!() + }; + + let mem = self.new_stack(pos, tupl.into()); + let mut offs = OffsetIter::new(tupl, self.tys); + for value in values { + let (ty, offset) = offs.next_ty(self.tys).unwrap(); + let mem = self.offset(mem, offset); + self.ci.nodes.unlock(value.id); + self.store_mem(mem, ty, value.id); + } + + Some(Value::ptr(mem).ty(tupl.into())) + } + Some(ty::Kind::Struct(s)) => { + let mem = self.new_stack(pos, s.into()); let mut offs = OffsetIter::new(s, self.tys); for field in fields { let Some((ty, offset)) = offs.next_ty(self.tys) else { @@ -1617,16 +1681,17 @@ impl<'a> Codegen<'a> { (append them to the end of the constructor)"), ); } - Some(Value::ptr(mem).ty(sty)) + Some(Value::ptr(mem).ty(s.into())) } - ty::Kind::Slice(s) => { + Some(ty::Kind::Slice(s)) => { let slice = &self.tys.ins.slices[s]; let len = slice.len().unwrap_or(fields.len()); let elem = slice.elem; let elem_size = self.tys.size_of(elem); - let aty = slice - .len() - .map_or_else(|| self.tys.make_array(elem, len as ArrayLen), |_| sty); + let aty = slice.len().map_or_else( + || self.tys.make_array(elem, len as ArrayLen), + |_| s.into(), + ); if len != fields.len() { return self.error( @@ -1651,13 +1716,13 @@ impl<'a> Codegen<'a> { Some(Value::ptr(mem).ty(aty)) } - _ => self.error( + Some(t) => self.error( pos, fa!( "the {}type of the constructor is `{}`, \ but thats not a struct nor slice or array", if ty.is_some() { "" } else { "inferred " }, - self.ty_display(sty), + self.ty_display(t.compress()), ), ), } @@ -2393,6 +2458,7 @@ impl<'a> Codegen<'a> { | ty::Kind::Func(_) | ty::Kind::Template(_) | ty::Kind::Global(_) + | ty::Kind::Tuple(_) | ty::Kind::Const(_)) => self.error( pos, fa!( @@ -4146,6 +4212,7 @@ mod tests { loops; pointers; structs; + tuples; struct_scopes; enums; unions; diff --git a/lang/src/ty.rs b/lang/src/ty.rs index 8262e833..2d4a61ef 100644 --- a/lang/src/ty.rs +++ b/lang/src/ty.rs @@ -38,9 +38,9 @@ pub type Offset = u32; pub type Size = u32; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)] -pub struct Tuple(pub u32); +pub struct List(pub u32); -impl Tuple { +impl List { const LEN_BITS: u32 = 5; const LEN_MASK: usize = Self::MAX_LEN - 1; const MAX_LEN: usize = 1 << Self::LEN_BITS; @@ -104,6 +104,12 @@ impl ArgIter { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Id(NonZeroU32); +impl AsRef for Id { + fn as_ref(&self) -> &Id { + self + } +} + impl From for i64 { fn from(value: Id) -> Self { value.0.get() as _ @@ -150,6 +156,7 @@ impl crate::ctx_map::CtxEntry for Id { SymKey::Decl(gb.file.into(), gb.name) } Kind::Slice(s) => SymKey::Array(&ctx.slices[s]), + Kind::Tuple(t) => SymKey::Tuple(ctx.tuples[t].fields), Kind::Module(_) | Kind::Builtin(_) => { SymKey::Decl(Module::default().into(), Ident::INVALID) } @@ -266,22 +273,19 @@ impl Id { } pub(crate) fn loc(&self, tys: &Types) -> Loc { + use Kind as K; match self.expand() { - Kind::Opt(o) + K::Opt(o) if let ty = tys.ins.opts[o].base && ty.loc(tys) == Loc::Reg && (ty.is_pointer() || tys.size_of(ty) < 8) => { Loc::Reg } - Kind::Ptr(_) | Kind::Enum(_) | Kind::Builtin(_) => Loc::Reg, - Kind::Struct(_) | Kind::Union(_) if tys.size_of(*self) == 0 => Loc::Reg, - Kind::Struct(_) | Kind::Union(_) | Kind::Slice(_) | Kind::Opt(_) => Loc::Stack, - c @ (Kind::Func(_) - | Kind::Global(_) - | Kind::Module(_) - | Kind::Const(_) - | Kind::Template(_)) => { + K::Ptr(_) | K::Enum(_) | K::Builtin(_) => Loc::Reg, + K::Struct(_) | K::Tuple(_) | K::Union(_) if tys.size_of(*self) == 0 => Loc::Reg, + K::Struct(_) | K::Tuple(_) | K::Union(_) | K::Slice(_) | K::Opt(_) => Loc::Stack, + c @ (K::Func(_) | K::Global(_) | K::Module(_) | K::Const(_) | K::Template(_)) => { unreachable!("{c:?}") } } @@ -450,6 +454,7 @@ type_kind! { pub enum Kind { Builtin, Struct, + Tuple, Enum, Union, Ptr, @@ -514,25 +519,25 @@ impl<'a> Display<'a> { impl core::fmt::Display for Display<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - use Kind as TK; - match TK::from_ty(self.ty) { - TK::Module(idx) => { + use Kind as K; + match K::from_ty(self.ty) { + K::Module(idx) => { f.write_str("@use(\"")?; self.files[idx].path.fmt(f)?; f.write_str(")[")?; idx.fmt(f)?; f.write_str("]") } - TK::Builtin(ty) => f.write_str(to_str(ty)), - TK::Opt(ty) => { + K::Builtin(ty) => f.write_str(to_str(ty)), + K::Opt(ty) => { f.write_str("?")?; self.rety(self.tys.ins.opts[ty].base).fmt(f) } - TK::Ptr(ty) => { + K::Ptr(ty) => { f.write_str("^")?; self.rety(self.tys.ins.ptrs[ty].base).fmt(f) } - TK::Struct(idx) => { + K::Struct(idx) => { let record = &self.tys.ins.structs[idx]; if record.name.is_null() { f.write_str("[")?; @@ -554,7 +559,19 @@ impl core::fmt::Display for Display<'_> { f.write_str(file.ident_str(record.name)) } } - TK::Union(idx) => { + K::Tuple(idx) => { + f.write_str(".(")?; + for (i, &ty) in + self.tys.ins.args[self.tys.ins.tuples[idx].fields.range()].iter().enumerate() + { + if i != 0 { + f.write_str(", ")?; + } + self.rety(ty).fmt(f)?; + } + f.write_str(")") + } + K::Union(idx) => { let record = &self.tys.ins.unions[idx]; if record.name.is_null() { f.write_str("[")?; @@ -576,27 +593,27 @@ impl core::fmt::Display for Display<'_> { f.write_str(file.ident_str(record.name)) } } - TK::Enum(idx) => { + K::Enum(idx) => { let enm = &self.tys.ins.enums[idx]; debug_assert!(!enm.name.is_null()); let file = &self.files[enm.file]; f.write_str(file.ident_str(enm.name)) } - TK::Func(idx) => { + K::Func(idx) => { f.write_str("fn")?; idx.fmt(f) } - TK::Template(idx) => { + K::Template(idx) => { f.write_str("fn")?; idx.fmt(f) } - TK::Global(idx) => { + K::Global(idx) => { let global = &self.tys.ins.globals[idx]; let file = &self.files[global.file]; f.write_str(file.ident_str(global.name))?; f.write_str(" (global)") } - TK::Slice(idx) => { + K::Slice(idx) => { let array = self.tys.ins.slices[idx]; f.write_str("[")?; self.rety(array.elem).fmt(f)?; @@ -606,7 +623,7 @@ impl core::fmt::Display for Display<'_> { } f.write_str("]") } - TK::Const(idx) => { + K::Const(idx) => { let cnst = &self.tys.ins.consts[idx]; let file = &self.files[cnst.file]; f.write_str(file.ident_str(cnst.name))?; @@ -618,9 +635,10 @@ impl core::fmt::Display for Display<'_> { #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub enum SymKey<'a> { + Tuple(List), Pointer(&'a PtrData), Optional(&'a OptData), - Type(Id, Pos, Tuple), + Type(Id, Pos, List), Decl(Id, Ident), Array(&'a ArrayData), Constant(&'a ConstData), @@ -628,7 +646,7 @@ pub enum SymKey<'a> { #[derive(Clone, Copy, Default)] pub struct Sig { - pub args: Tuple, + pub args: List, pub ret: Id, } @@ -722,7 +740,7 @@ pub struct TypeBase { pub pos: Pos, pub name: Ident, pub field_start: u32, - pub captured: Tuple, + pub captured: List, pub ast: ExprRef, } @@ -764,6 +782,13 @@ pub struct StructData { impl_deref!(StructData { base: TypeBase }); +#[derive(Default)] +pub struct TupleData { + pub fields: List, + pub size: Cell, + pub align: Cell, +} + #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub struct OptData { pub base: Id, @@ -854,6 +879,7 @@ pub struct TypeIns { pub ptrs: EntVec, pub opts: EntVec, pub slices: EntVec, + pub tuples: EntVec, } pub struct FTask { @@ -897,6 +923,7 @@ impl Types { | Kind::Builtin(_) | Kind::Ptr(_) | Kind::Slice(_) + | Kind::Tuple(_) | Kind::Opt(_) => utils::is_pascal_case, Kind::Func(f) if let &Expr::Closure { ret: &Expr::Ident { id, .. }, .. } = @@ -919,19 +946,19 @@ impl Types { } } - pub fn pack_args(&mut self, arg_base: usize) -> Option { + pub fn pack_args(&mut self, arg_base: usize) -> Option { let base = self.ins.args.len(); self.ins.args.extend(self.tmp.args.drain(arg_base..)); let needle = &self.ins.args[base..]; if needle.is_empty() { - return Some(Tuple::empty()); + return Some(List::empty()); } let len = needle.len(); // FIXME: maybe later when this becomes a bottleneck we use more // efficient search (SIMD?, indexing?) let sp = self.ins.args.windows(needle.len()).position(|val| val == needle).unwrap(); self.ins.args.truncate((sp + needle.len()).max(base)); - Tuple::new(sp, len) + List::new(sp, len) } pub fn union_fields(&self, union: Union) -> &[UnionField] { @@ -1014,6 +1041,16 @@ impl Types { self.ins.structs[stru].size.set(oiter.offset); oiter.offset } + Kind::Tuple(tuple) => { + if self.ins.tuples[tuple].size.get() != 0 { + return self.ins.tuples[tuple].size.get(); + } + + let mut oiter = OffsetIter::new(tuple, self); + while oiter.next(self).is_some() {} + self.ins.tuples[tuple].size.set(oiter.offset); + oiter.offset + } Kind::Union(union) => { if self.ins.unions[union].size.get() != 0 { return self.ins.unions[union].size.get(); @@ -1033,8 +1070,12 @@ impl Types { self.size_of(base) + self.align_of(base) } } - _ if let Some(size) = ty.simple_size() => size, - ty => unimplemented!("size_of: {:?}", ty), + Kind::Ptr(_) | Kind::Builtin(_) => ty.simple_size().unwrap(), + Kind::Func(_) + | Kind::Template(_) + | Kind::Global(_) + | Kind::Const(_) + | Kind::Module(_) => unreachable!(), } } @@ -1066,6 +1107,15 @@ impl Types { self.ins.structs[stru].align.set(align.try_into().unwrap()); align } + Kind::Tuple(tuple) => { + if self.ins.tuples[tuple].align.get() != 0 { + return self.ins.tuples[tuple].align.get() as _; + } + let align = + self.tuple_fields(tuple).iter().map(|&f| self.align_of(f)).max().unwrap_or(1); + self.ins.tuples[tuple].align.set(align.try_into().unwrap()); + align + } Kind::Slice(arr) => { let arr = &self.ins.slices[arr]; match arr.len { @@ -1073,7 +1123,14 @@ impl Types { _ => self.align_of(arr.elem), } } - _ => self.size_of(ty).max(1), + Kind::Opt(opt) => self.align_of(self.ins.opts[opt].base), + Kind::Builtin(_) | Kind::Enum(_) | Kind::Ptr(_) => self.size_of(ty), + Kind::Func(_) + | Kind::Template(_) + | Kind::Global(_) + | Kind::Const(_) + | Kind::Module(_) => unreachable!(), + //_ => self.size_of(ty).max(1), } } @@ -1161,6 +1218,7 @@ impl Types { | Kind::Template(_) | Kind::Global(_) | Kind::Module(_) + | Kind::Tuple(_) | Kind::Const(_) => return None, }) } @@ -1186,7 +1244,7 @@ impl Types { self.type_base_of(ty).map(|b| b.parent) } - pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], Tuple)> { + pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], List)> { let base = self.type_base_of(ty)?; let (Expr::Struct { captured, .. } @@ -1212,6 +1270,10 @@ impl Types { let str = unsafe { core::mem::transmute::<&mut Vec, &mut String>(data) }; write!(str, "{}", Display::new(self, files, ty)).unwrap(); } + + pub fn tuple_fields(&self, tuple: Tuple) -> &[Id] { + &self.ins.args[self.ins.tuples[tuple].fields.range()] + } } pub struct OptLayout { @@ -1220,17 +1282,57 @@ pub struct OptLayout { pub payload_offset: Offset, } -pub struct OffsetIter { - strct: Struct, +pub trait Agregate: Copy { + type Field: AsRef + 'static; + + fn fields(self, tys: &Types) -> Range; + fn field_by_idx(tys: &Types, index: usize) -> &Self::Field; + fn align_override(self, _: &Types) -> Option { + None + } +} + +impl Agregate for Tuple { + type Field = Id; + + fn fields(self, tys: &Types) -> Range { + tys.ins.tuples[self].fields.range() + } + + fn field_by_idx(tys: &Types, index: usize) -> &Self::Field { + &tys.ins.args[index] + } +} + +impl Agregate for Struct { + type Field = StructField; + + fn fields(self, tys: &Types) -> Range { + tys.struct_field_range(self) + } + + fn field_by_idx(tys: &Types, index: usize) -> &Self::Field { + &tys.ins.struct_fields[index] + } + + fn align_override(self, tys: &Types) -> Option { + tys.ins.structs[self].explicit_alignment + } +} + +impl AsRef for StructField { + fn as_ref(&self) -> &Id { + &self.ty + } +} + +pub struct OffsetIter { + strct: T, offset: Offset, fields: Range, } -impl OffsetIter { - pub fn new(strct: Struct, tys: &Types) -> Self { - Self { strct, offset: 0, fields: tys.struct_field_range(strct) } - } - +impl OffsetIter { pub fn offset_of(tys: &Types, idx: Struct, field: &str) -> Option<(Offset, Id)> { let field_id = tys.names.project(field)?; OffsetIter::new(idx, tys) @@ -1238,25 +1340,33 @@ impl OffsetIter { .find(|(f, _)| f.name == field_id) .map(|(f, off)| (off, f.ty)) } +} - fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a StructField, Offset)> { - let stru = &tys.ins.structs[self.strct]; - let field = &tys.ins.struct_fields[self.fields.next()?]; +impl OffsetIter { + pub fn new(strct: T, tys: &Types) -> Self { + Self { strct, offset: 0, fields: strct.fields(tys) } + } - let align = stru.explicit_alignment.map_or_else(|| tys.align_of(field.ty), |a| a as u32); + fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a T::Field, Offset)> { + let field = &T::field_by_idx(tys, self.fields.next()?); + + let align = self + .strct + .align_override(tys) + .map_or_else(|| tys.align_of(*field.as_ref()), |a| a as u32); self.offset = (self.offset + align - 1) & !(align - 1); let off = self.offset; - self.offset += tys.size_of(field.ty); + self.offset += tys.size_of(*field.as_ref()); Some((field, off)) } pub fn next_ty(&mut self, tys: &Types) -> Option<(Id, Offset)> { let (field, off) = self.next(tys)?; - Some((field.ty, off)) + Some((*field.as_ref(), off)) } - pub fn into_iter(mut self, tys: &Types) -> impl Iterator { + pub fn into_iter(mut self, tys: &Types) -> impl Iterator { core::iter::from_fn(move || self.next(tys)) } } diff --git a/lang/tests/son_tests_tuples.txt b/lang/tests/son_tests_tuples.txt new file mode 100644 index 00000000..5aa16aa1 --- /dev/null +++ b/lang/tests/son_tests_tuples.txt @@ -0,0 +1,6 @@ +main: + CP r1, r0 + JALA r0, r31, 0a +code size: 22 +ret: 0 +status: Ok(())