1 /**
2     Variants
3     
4     Copyright:
5         Copyright © 2023-2025, Kitsunebi Games
6         Copyright © 2023-2025, Inochi2D Project
7     
8     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Authors:
10         Luna Nielsen
11 */
12 module nulib.data.variant;
13 import numem.core.traits;
14 import numem.core.exception;
15 import numem.core.hooks;
16 import numem.optional;
17 import numem.lifetime;
18 
19 /**
20     A type which can contain any type in the language, stored on the heap.
21 
22     Note:
23         The ownership of a variant depends on the API, the $(D Variant) itself
24         does not own its memory. You may call the $(D free) method to free any
25         memory owned by the variant.
26 */
27 struct Variant {
28 private:
29     // Helpers that are not nogc, to allow usage with druntime.
30     static bool isType(T)(TypeInfo id) => id == typeid(T);
31     static string getTypeName(TypeInfo id) => id.toString();
32 
33 @nogc:
34     TypeInfo id;
35     void* data;
36 
37     // Internal ctor
38     this(TypeInfo id, void* data) @trusted nothrow {
39         this.id = id;
40         this.data = data;
41     }
42 
43 public:
44     alias isInitialized this;
45 
46     /**
47         An empty variant.
48     */
49     enum empty = Variant(null, null);
50 
51     /**
52         Whether the variant is empty.
53     */
54     @property bool isEmpty() @trusted nothrow pure => id is null || data is null;
55 
56     /**
57         Whether the variant has data.
58     */
59     @property bool isInitialized() @trusted nothrow pure => id !is null && data !is null;
60 
61     /**
62         Constructor
63     */
64     this(T)(auto ref T value) @trusted nothrow {
65         static if (is(T == Variant)) {
66             this.id = value.id;
67             this.data = value.data;
68         } else {
69             this.id = typeid(T);
70             static if (isHeapAllocated!T)
71                 this.data = cast(void*)value;
72             else {
73                 this.data = nu_malloc(T.sizeof);
74                 *(cast(T*)this.data) = value.move();
75             }
76         }
77     }
78 
79     /**
80         Gets whether the variant contains the given type.
81 
82         Returns:
83             Whether the variant is storing a type with the given
84             type's type id.
85     */
86     bool has(T)() @trusted nothrow {
87         return id !is null && assumeNoThrowNoGC(&isType!T, id);
88     }
89 
90     /**
91         Tries to get the content of the variant.
92 
93         Returns:
94             A $(D Option) value either containing the value
95             or invalid state. 
96     */
97     Option!T tryGet(T)() @trusted nothrow {
98         if (!has!(T)())
99             return none!T();
100         
101         static if (isHeapAllocated!T)
102             return some!T(cast(T)data);
103         else
104             return some!T(*cast(T*)data);
105     }
106 
107     /**
108         Gets the content of the variant.
109 
110         Note:
111             This function will fatally crash if the variant
112             doesn't contain a value of the given type!
113     */
114     T get(T)() @trusted nothrow {
115         if (!has!(T)()) {
116             import nulib.string : nstring;
117             nu_fatal(nstring("Type mismatch between ", T.stringof, " and ", assumeNoThrowNoGC(&getTypeName, id), "!").take());
118         }
119         
120         static if (isHeapAllocated!T)
121             return cast(T)data;
122         else
123             return *cast(T*)data;
124     }
125 
126     /// Allows assigning the variant to a value.
127     void opAssign(T)(auto ref T value) @trusted nothrow {
128         static if (is(T == Variant)) {
129             this.id = value.id;
130             this.data = value.data;
131         } else {
132             this.id = typeid(T);
133             static if (isHeapAllocated!T)
134                 this.data = cast(void*)value;
135             else {
136                 this.data = nu_malloc(T.sizeof);
137                 *(cast(T*)this.data) = value.move();
138             }
139         }
140     }
141 
142     /**
143         Gets whether the variant is the same as another variant.
144     */
145     bool opEquals(T)(T other) @trusted nothrow {
146         return other.data is data;
147     }
148 
149     /**
150         Frees the memory stored in the variant.
151 
152         Note:
153             This will NOT call any destructors on the type in
154             question. 
155     */
156     void free() @trusted nothrow {
157         this.id = null;
158         if (data) {
159             nu_free(data);
160             this.data = null;
161         }
162     }
163 }
164 
165 @("Variant: get, has, assign")
166 unittest {
167 
168     // ctor initialize.
169     Variant v = 42;
170     assert(v);
171     assert(v.has!int);
172     assert(!v.has!long);
173     assert(v.tryGet!int);
174     assert((v.tryGet!int).get == 42);
175     v.free();
176 
177     // assign initialize
178     v = 42;
179     assert(v);
180     assert(v.has!int);
181     assert(v.tryGet!int);
182     assert((v.tryGet!int).get == 42);
183     Variant v2 = v;
184     assert(v == v2); // Is it a reference to the same data?
185 
186     // Free one and empty the other.
187     v.free();
188     v2 = Variant.empty;
189     assert(v == v2 && !v);
190 }
191 
192 @("Variant: arrays")
193 unittest {
194     Variant v = [0, 1, 2, 3].nu_dup();
195     assert(v);
196     assert(v.has!(int[]));
197     assert(v.tryGet!(int[]).get == [0, 1, 2, 3]);
198     v.free();
199 }