Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
66d1f30b5a
|
|||
|
260bea9ff1
|
@@ -242,6 +242,9 @@ pub enum PrimOpPhase {
|
|||||||
ForceResultShallowLoop,
|
ForceResultShallowLoop,
|
||||||
ForceResultDeepFinish,
|
ForceResultDeepFinish,
|
||||||
|
|
||||||
|
// TODO: split into separate enums
|
||||||
|
CallPattern,
|
||||||
|
|
||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn inline_maybe_thunk(&mut self, val: MaybeThunk) -> InlineOperand {
|
fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand {
|
||||||
use MaybeThunk::*;
|
use MaybeThunk::*;
|
||||||
match val {
|
match val {
|
||||||
Int(x) => {
|
Int(x) => {
|
||||||
@@ -812,14 +812,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
for &(_key, _val, _span) in dyns.iter() {
|
for &(_key, val, span) in dyns.iter() {
|
||||||
todo!("redesign dynamic attr key");
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||||
// self.emit_u8(AttrKeyType::Dynamic as u8);
|
self.emit_maybe_thunk(val);
|
||||||
// self.emit_maybe_thunk(key);
|
let span_id = self.ctx.register_span(span);
|
||||||
// let val_operand = self.inline_maybe_thunk(val);
|
self.emit_u32(span_id);
|
||||||
// self.emit_maybe_thunk(val);
|
|
||||||
// let span_id = self.ctx.register_span(span);
|
|
||||||
// self.emit_u32(span_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use fix_common::StringId;
|
|||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use string_interner::Symbol as _;
|
use string_interner::Symbol as _;
|
||||||
|
|
||||||
use crate::{OperandData, VmRuntimeCtx};
|
use crate::{AttrKeyData, OperandData, VmRuntimeCtx};
|
||||||
|
|
||||||
pub(crate) struct BytecodeReader<'a> {
|
pub(crate) struct BytecodeReader<'a> {
|
||||||
bytecode: &'a [u8],
|
bytecode: &'a [u8],
|
||||||
@@ -117,14 +117,14 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_attr_key_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> crate::AttrKeyData {
|
pub(crate) fn read_attr_key_data(&mut self) -> crate::AttrKeyData {
|
||||||
use fix_codegen::AttrKeyType;
|
use fix_codegen::AttrKeyType;
|
||||||
let tag = self.read_u8();
|
let tag = self.read_u8();
|
||||||
let ty = AttrKeyType::try_from_primitive(tag)
|
let ty = AttrKeyType::try_from_primitive(tag)
|
||||||
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
||||||
match ty {
|
match ty {
|
||||||
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
|
AttrKeyType::Static => AttrKeyData::Static(self.read_string_id()),
|
||||||
AttrKeyType::Dynamic => crate::AttrKeyData::Dynamic(self.read_operand_data(ctx)),
|
AttrKeyType::Dynamic => AttrKeyData::Dynamic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
||||||
|
let a = a.entries.borrow();
|
||||||
|
let b = b.entries.borrow();
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use fix_builtins::PrimOpPhase;
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
use gc_arena::{Gc, Mutation};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
use num_enum::TryFromPrimitive as _;
|
use num_enum::TryFromPrimitive as _;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
@@ -37,6 +37,8 @@ impl<'gc> Vm<'gc> {
|
|||||||
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
||||||
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
||||||
|
|
||||||
|
CallPattern => self.primop_call_pattern(ctx, reader, mc),
|
||||||
|
|
||||||
phase => todo!("primop phase {phase:?}"),
|
phase => todo!("primop phase {phase:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,6 +165,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
let e1 = self.peek_forced(1);
|
let e1 = self.peek_forced(1);
|
||||||
|
|
||||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||||
|
let attrs = attrs.entries.borrow();
|
||||||
if attrs.is_empty() {
|
if attrs.is_empty() {
|
||||||
SmallVec::new()
|
SmallVec::new()
|
||||||
} else {
|
} else {
|
||||||
@@ -240,6 +243,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
let mut added: usize = 0;
|
let mut added: usize = 0;
|
||||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||||
|
let attrs = attrs.entries.borrow();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||||
if !self.is_value_in_seen(seen, item) {
|
if !self.is_value_in_seen(seen, item) {
|
||||||
@@ -287,7 +291,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
let val = self.peek_forced(0);
|
let val = self.peek_forced(0);
|
||||||
|
|
||||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
let len = attrs.len();
|
let len = attrs.entries.borrow().len();
|
||||||
(len, len > 0)
|
(len, len > 0)
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
let len = list.inner.borrow().len();
|
let len = list.inner.borrow().len();
|
||||||
@@ -327,7 +331,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
let val = self.peek_forced(2);
|
let val = self.peek_forced(2);
|
||||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
attrs.get(idx as usize).map(|&(_, v)| v)
|
attrs.entries.borrow().get(idx as usize).map(|&(_, v)| v)
|
||||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||||
list.inner.borrow().get(idx as usize).copied()
|
list.inner.borrow().get(idx as usize).copied()
|
||||||
} else {
|
} else {
|
||||||
@@ -368,6 +372,56 @@ impl<'gc> Vm<'gc> {
|
|||||||
self.finish_ok(ctx.convert_value(val.relax()))
|
self.finish_ok(ctx.convert_value(val.relax()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_call_pattern(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let (func, attrset) = self.try_force::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||||
|
|
||||||
|
let Closure {
|
||||||
|
ip,
|
||||||
|
n_locals,
|
||||||
|
env,
|
||||||
|
pattern,
|
||||||
|
} = *func;
|
||||||
|
let Some(pattern) = pattern else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
// TODO: get function name
|
||||||
|
// TODO: param spans
|
||||||
|
if !pattern.ellipsis {
|
||||||
|
for key in pattern.required.iter().copied() {
|
||||||
|
if attrset.lookup(key).is_none() {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called without required argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(key, _) in attrset.entries.borrow().iter() {
|
||||||
|
let is_expected =
|
||||||
|
pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||||
|
if !is_expected {
|
||||||
|
let name = ctx.resolve_string(key);
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_env = Gc::new(
|
||||||
|
mc,
|
||||||
|
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||||
|
);
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
self.env = new_env;
|
||||||
|
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||||
if !is_container(val) {
|
if !is_container(val) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -23,13 +23,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
self.call_depth += 1;
|
self.call_depth += 1;
|
||||||
if let Some(closure) = func.as_gc::<Closure>() {
|
if let Some(closure) = func.as_gc::<Closure>() {
|
||||||
let ip = closure.ip;
|
if closure.pattern.is_some() {
|
||||||
let n_locals = closure.n_locals;
|
// FIXME: better DX...
|
||||||
let env = closure.env;
|
self.push(func.relax());
|
||||||
if let Some(ref _pattern) = closure.pattern {
|
self.push(arg);
|
||||||
todo!("pattern call")
|
|
||||||
} else {
|
|
||||||
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
|
||||||
self.call_stack.push(CallFrame {
|
self.call_stack.push(CallFrame {
|
||||||
pc: resume_pc,
|
pc: resume_pc,
|
||||||
stack_depth: 0,
|
stack_depth: 0,
|
||||||
@@ -37,9 +34,23 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
env: self.env,
|
env: self.env,
|
||||||
with_env: self.with_env,
|
with_env: self.with_env,
|
||||||
});
|
});
|
||||||
reader.set_pc(ip as usize);
|
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
||||||
self.env = new_env;
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ip = closure.ip;
|
||||||
|
let n_locals = closure.n_locals;
|
||||||
|
let env = closure.env;
|
||||||
|
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: resume_pc,
|
||||||
|
stack_depth: 0,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
reader.set_pc(ip as usize);
|
||||||
|
self.env = new_env;
|
||||||
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
||||||
if primop.arity == 1 {
|
if primop.arity == 1 {
|
||||||
self.push(arg);
|
self.push(arg);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
let count = reader.read_u32() as usize;
|
let count = reader.read_u32() as usize;
|
||||||
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
let key = reader.read_attr_key_data(ctx);
|
let key = reader.read_attr_key_data();
|
||||||
let val = reader.read_operand_data(ctx);
|
let val = reader.read_operand_data(ctx);
|
||||||
let _span_id = reader.read_u32();
|
let _span_id = reader.read_u32();
|
||||||
entries.push(AttrEntry { key, val });
|
entries.push(AttrEntry { key, val });
|
||||||
@@ -29,10 +29,8 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
let key_sid = match &entry.key {
|
let key_sid = match &entry.key {
|
||||||
AttrKeyData::Static(sid) => *sid,
|
AttrKeyData::Static(sid) => *sid,
|
||||||
AttrKeyData::Dynamic(op) => {
|
AttrKeyData::Dynamic => {
|
||||||
let v = op.resolve(mc, self);
|
todo!()
|
||||||
v.as_inline::<crate::StringId>()
|
|
||||||
.expect("dynamic attr key must be a string")
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = entry.val.resolve(mc, self);
|
let val = entry.val.resolve(mc, self);
|
||||||
|
|||||||
+3
-3
@@ -143,14 +143,14 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|||||||
Value::String(ns.as_str().to_owned())
|
Value::String(ns.as_str().to_owned())
|
||||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||||
let bits = val.to_bits();
|
let bits = val.to_bits();
|
||||||
if attrs.is_empty() {
|
if attrs.entries.borrow().is_empty() {
|
||||||
return Value::AttrSet(Default::default());
|
return Value::AttrSet(Default::default());
|
||||||
}
|
}
|
||||||
if !seen.insert(bits) {
|
if !seen.insert(bits) {
|
||||||
return Value::Repeated;
|
return Value::Repeated;
|
||||||
}
|
}
|
||||||
let mut map = std::collections::BTreeMap::new();
|
let mut map = std::collections::BTreeMap::new();
|
||||||
for &(key, val) in attrs.iter() {
|
for &(key, val) in attrs.entries.borrow().iter() {
|
||||||
let key = self.resolve_string(key).to_owned();
|
let key = self.resolve_string(key).to_owned();
|
||||||
let converted = self.convert_value_with_seen(val, seen);
|
let converted = self.convert_value_with_seen(val, seen);
|
||||||
map.insert(fix_common::Symbol::from(key), converted);
|
map.insert(fix_common::Symbol::from(key), converted);
|
||||||
@@ -252,7 +252,7 @@ impl OperandData {
|
|||||||
|
|
||||||
pub(crate) enum AttrKeyData {
|
pub(crate) enum AttrKeyData {
|
||||||
Static(StringId),
|
Static(StringId),
|
||||||
Dynamic(OperandData),
|
Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
||||||
|
|||||||
+32
-19
@@ -435,65 +435,78 @@ impl fmt::Debug for NixString {
|
|||||||
#[derive(Collect, Debug, Default)]
|
#[derive(Collect, Debug, Default)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct AttrSet<'gc> {
|
pub(crate) struct AttrSet<'gc> {
|
||||||
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
pub(crate) entries: RefLock<SmallVec<[(StringId, Value<'gc>); 4]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Deref for AttrSet<'gc> {
|
impl<'gc> Unlock for AttrSet<'gc> {
|
||||||
type Target = [(StringId, Value<'gc>)];
|
type Unlocked = RefCell<SmallVec<[(StringId, Value<'gc>); 4]>>;
|
||||||
fn deref(&self) -> &Self::Target {
|
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||||
&self.entries
|
unsafe { self.entries.unlock_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
Self { entries }
|
Self {
|
||||||
|
entries: RefLock::new(entries),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||||
self.entries
|
let entries = self.entries.borrow();
|
||||||
|
entries
|
||||||
.binary_search_by_key(&key, |(k, _)| *k)
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|i| self.entries[i].1)
|
.map(|i| entries[i].1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has(&self, key: StringId) -> bool {
|
pub(crate) fn has(&self, key: StringId) -> bool {
|
||||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
self.entries
|
||||||
|
.borrow()
|
||||||
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
let self_entries = self.entries.borrow();
|
||||||
debug_assert!(other.entries.is_sorted_by_key(|(key, _)| *key));
|
let other_entries = other.entries.borrow();
|
||||||
|
debug_assert!(self_entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
debug_assert!(other_entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
|
||||||
let mut entries = SmallVec::new();
|
let mut entries = SmallVec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut j = 0;
|
let mut j = 0;
|
||||||
while i < self.entries.len() && j < other.entries.len() {
|
while i < self_entries.len() && j < other_entries.len() {
|
||||||
match self.entries[i].0.cmp(&other.entries[j].0) {
|
match self_entries[i].0.cmp(&other_entries[j].0) {
|
||||||
Less => {
|
Less => {
|
||||||
entries.push(self.entries[i]);
|
entries.push(self_entries[i]);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
Greater => {
|
Greater => {
|
||||||
entries.push(other.entries[j]);
|
entries.push(other_entries[j]);
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
Equal => {
|
Equal => {
|
||||||
entries.push(other.entries[j]);
|
entries.push(other_entries[j]);
|
||||||
i += 1;
|
i += 1;
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries.extend(other.entries[j..].iter().cloned());
|
entries.extend(other_entries[j..].iter().cloned());
|
||||||
entries.extend(self.entries[i..].iter().cloned());
|
entries.extend(self_entries[i..].iter().cloned());
|
||||||
|
|
||||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
|
|
||||||
Gc::new(mc, AttrSet { entries })
|
Gc::new(
|
||||||
|
mc,
|
||||||
|
AttrSet {
|
||||||
|
entries: RefLock::new(entries),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user