lua_types/gc.rs
1//! `GcRef<T>` — the GC-managed reference handle.
2//!
3//! Phase A/B/C: thin newtype around `Rc<T>`.
4//! Phase D-1e (current): newtype around `lua_gc::Gc<T>` — Copy under the hood,
5//! tracks allocation in the active `Heap` (via `lua_gc::with_current_heap(...)`).
6//!
7//! Surface kept stable across the swap: `new`, `ptr_eq`, `identity`,
8//! `strong_count`, `weak_count`, `downgrade`. Existing code touching
9//! `gc.0` continues to work — `.0` is now `Gc<T>` instead of `Rc<T>`.
10//!
11//! # Weak refs (D-1)
12//!
13//! `GcWeak<T>` is currently a no-op wrapper: `upgrade` always returns
14//! `Some`, `strong_count` always returns `1`. Real weak semantics arrive
15//! in D-2 when the heap learns to mark weak refs separately. For D-1, weak
16//! tables ARE a known semantic gap (see PHASE_D_PLAN.md "Locked Decisions").
17
18use lua_gc::{Gc, Marker, Trace};
19
20/// A GC-managed pointer to a Lua collectable object. Newtype over
21/// `lua_gc::Gc<T>` so callers preserve `gc.0`-shape access while the
22/// backend swaps under them.
23#[derive(Debug)]
24pub struct GcRef<T: Trace + 'static>(pub Gc<T>);
25
26impl<T: Trace + 'static> GcRef<T> {
27 /// Allocate a new GC-tracked value. If a `HeapGuard` is active (set by
28 /// `state.run()` / `pcall_k`), the new allocation joins that heap's
29 /// allgc chain. Otherwise it allocates "uncollected" — leaks until
30 /// process exit, same as the old `Rc::new` behavior.
31 pub fn new(value: T) -> Self {
32 let gc = lua_gc::with_current_heap(|heap| match heap {
33 Some(heap) => heap.allocate(value),
34 None => Gc::new_uncollected(value),
35 });
36 GcRef(gc)
37 }
38
39 /// Cycle-aware trace dispatch.
40 ///
41 /// During D-0/D-1 (before a real Color::Gray flag is reachable from
42 /// inside Trace impls), `Marker::try_visit` records the underlying
43 /// allocation's identity and short-circuits a second recursion. Once
44 /// D-2 ships the in-header color flag, this helper collapses to
45 /// `m.mark(self.0)`.
46 pub fn trace_obj(&self, m: &mut Marker) {
47 if m.try_visit(self.identity()) {
48 (**self).trace(m);
49 }
50 }
51}
52
53impl<T: Trace + 'static> GcRef<T> {
54 /// Two `GcRef`s are identity-equal iff they point at the same box.
55 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
56 Gc::ptr_eq(a.0, b.0)
57 }
58
59 /// Identity as a usize — used as a HashMap key for "same object" tests.
60 pub fn identity(&self) -> usize {
61 self.0.identity()
62 }
63
64 /// Number of strong references. Phase D-1: always returns 1 (no
65 /// refcount semantics). Real value once weak refs land in D-2.
66 pub fn strong_count(&self) -> usize {
67 1
68 }
69
70 /// Number of weak references. Phase D-1: always returns 0.
71 pub fn weak_count(&self) -> usize {
72 0
73 }
74
75 /// Get a weak handle. Phase D-1: GcWeak is a thin wrapper that always
76 /// upgrades; real weak semantics arrive in D-2.
77 pub fn downgrade(&self) -> GcWeak<T> {
78 GcWeak(self.0)
79 }
80
81 /// Charge (`delta > 0`) or refund (`delta < 0`) bytes of this object's
82 /// owned heap buffers against the active heap's pacer, so collections
83 /// fire at honest memory pressure. No-op on `delta == 0`, when no heap is
84 /// active, or when the underlying box is uncollected (see
85 /// [`lua_gc::Gc::account_buffer`]).
86 pub fn account_buffer(&self, delta: isize) {
87 if delta == 0 {
88 return;
89 }
90 lua_gc::with_current_heap(|h| {
91 if let Some(h) = h {
92 self.0.account_buffer(h, delta)
93 }
94 })
95 }
96}
97
98/// A weak handle to a `GcRef<T>`. Phase D-1 placeholder; D-2 will give
99/// this real semantics (None once the referent is swept).
100#[derive(Debug)]
101pub struct GcWeak<T: Trace + 'static>(pub Gc<T>);
102
103impl<T: Trace + 'static> GcWeak<T> {
104 /// Try to promote to a strong reference. Phase D-1: always Some
105 /// (weak semantics are not yet implemented).
106 pub fn upgrade(&self) -> Option<GcRef<T>> {
107 Some(GcRef(self.0))
108 }
109
110 /// Strong reference count of the target. Phase D-1: always 1.
111 pub fn strong_count(&self) -> usize {
112 1
113 }
114}
115
116impl<T: Trace + 'static> Clone for GcWeak<T> {
117 fn clone(&self) -> Self {
118 GcWeak(self.0)
119 }
120}
121
122impl<T: Trace + 'static> Clone for GcRef<T> {
123 fn clone(&self) -> Self {
124 GcRef(self.0)
125 }
126}
127
128impl<T: Trace + 'static> Copy for GcRef<T> {}
129
130impl<T: Trace + 'static> std::ops::Deref for GcRef<T> {
131 type Target = T;
132 fn deref(&self) -> &T {
133 &*self.0
134 }
135}
136
137impl<T: Trace + 'static> AsRef<T> for GcRef<T> {
138 fn as_ref(&self) -> &T {
139 &*self.0
140 }
141}
142
143impl<T: PartialEq + Trace + 'static> PartialEq for GcRef<T> {
144 fn eq(&self, other: &Self) -> bool {
145 Gc::ptr_eq(self.0, other.0) || **self == **other
146 }
147}
148
149// ──────────────────────────────────────────────────────────────────────────────
150// PORT STATUS
151// source: n/a (GcRef public wrapper around lua-gc::Gc<T>)
152// target_crate: lua-types
153// confidence: high
154// todos: 0
155// port_notes: 0
156// unsafe_blocks: 0
157// notes: Thin wrapper type so consumers across crates don't depend on lua-gc's
158// raw Gc<T>. Clone/Deref/PartialEq forwarded; no unsafe surface.
159// ──────────────────────────────────────────────────────────────────────────────