@@ -21,7 +21,6 @@ const ArenaAllocator = std.heap.ArenaAllocator;
2121
2222const log = std .log .scoped (.js );
2323
24-
2524// Global, should only be initialized once.
2625pub const Platform = struct {
2726 inner : v8.Platform ,
@@ -453,7 +452,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
453452 }
454453 }.callback );
455454
456- template .getInstanceTemplate ().setInternalFieldCount (1 );
455+ if (comptime isEmpty (Receiver (Struct )) == false ) {
456+ // If the struct is empty, we won't store a Zig reference inside
457+ // the JS object, so we don't need to set the internal field count
458+ template .getInstanceTemplate ().setInternalFieldCount (1 );
459+ }
457460
458461 const class_name = v8 .String .initUtf8 (self .isolate , comptime classNameForStruct (Struct ));
459462 template .setClassName (class_name );
@@ -973,19 +976,28 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
973976 else = > @compileError ("mapZigInstanceToJs requires a v8.Object (constructors) or v8.FunctionTemplate, got: " ++ @typeName (@TypeOf (js_obj_or_template ))),
974977 };
975978
976- // The TAO contains the pointer ot our Zig instance as
977- // well as any meta data we'll need to use it later.
978- // See the TaggedAnyOpaque struct for more details.
979- const tao = try scope_arena .create (TaggedAnyOpaque );
980- tao .* = .{
981- .ptr = value ,
982- .index = @field (TYPE_LOOKUP , @typeName (ptr .child )),
983- .sub_type = if (@hasDecl (ptr .child , "sub_type" )) ptr .child .sub_type else null ,
984- .offset = if (@typeInfo (ptr .child ) != .@"opaque" and @hasField (ptr .child , "proto" )) @offsetOf (ptr .child , "proto" ) else -1 ,
985- };
986-
987979 const isolate = self .isolate ;
988- js_obj .setInternalField (0 , v8 .External .init (isolate , tao ));
980+
981+ if (isEmpty (ptr .child ) == false ) {
982+ // The TAO contains the pointer ot our Zig instance as
983+ // well as any meta data we'll need to use it later.
984+ // See the TaggedAnyOpaque struct for more details.
985+ const tao = try scope_arena .create (TaggedAnyOpaque );
986+ tao .* = .{
987+ .ptr = value ,
988+ .index = @field (TYPE_LOOKUP , @typeName (ptr .child )),
989+ .sub_type = if (@hasDecl (ptr .child , "sub_type" )) ptr .child .sub_type else null ,
990+ .offset = if (@typeInfo (ptr .child ) != .@"opaque" and @hasField (ptr .child , "proto" )) @offsetOf (ptr .child , "proto" ) else -1 ,
991+ };
992+
993+ js_obj .setInternalField (0 , v8 .External .init (isolate , tao ));
994+ } else {
995+ // If the struct is empty, we don't need to do all
996+ // the TOA stuff and setting the internal data.
997+ // When we try to map this from JS->Zig, in
998+ // typeTaggedAnyOpaque, we'll also know there that
999+ // the type is empty and can create an empty instance.
1000+ }
9891001 const js_persistent = PersistentObject .init (isolate , js_obj );
9901002 gop .value_ptr .* = js_persistent ;
9911003 return js_persistent ;
@@ -1267,7 +1279,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
12671279
12681280 // Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
12691281 // contains a ptr to the correct type.
1270- fn typeTaggedAnyOpaque (comptime named_function : anytype , comptime R : type , op : ? * anyopaque ) ! R {
1282+ fn typeTaggedAnyOpaque (comptime named_function : anytype , comptime R : type , js_obj : v8.Object ) ! R {
12711283 const ti = @typeInfo (R );
12721284 if (ti != .pointer ) {
12731285 @compileError (std .fmt .comptimePrint (
@@ -1276,16 +1288,25 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
12761288 ));
12771289 }
12781290
1279- const type_name = @typeName (ti .pointer .child );
1291+ const T = ti .pointer .child ;
1292+ if (comptime isEmpty (T )) {
1293+ // Empty structs aren't stored as TOAs and there's no data
1294+ // stored in the JSObject's IntenrnalField. Why bother when
1295+ // we can just return an empty struct here?
1296+ return @constCast (@as (* const T , &.{}));
1297+ }
1298+
1299+ const type_name = @typeName (T );
12801300 if (@hasField (TypeLookup , type_name ) == false ) {
12811301 @compileError (std .fmt .comptimePrint (
12821302 "{s} has an unknown Zig type: {s}" ,
12831303 .{ named_function .full_name , @typeName (R ) },
12841304 ));
12851305 }
12861306
1307+ const op = js_obj .getInternalField (0 ).castTo (v8 .External ).get ();
12871308 const toa : * TaggedAnyOpaque = @alignCast (@ptrCast (op ));
1288- const expected_type_index = @field (TYPE_LOOKUP , @typeName ( ti . pointer . child ) );
1309+ const expected_type_index = @field (TYPE_LOOKUP , type_name );
12891310
12901311 var type_index = toa .index ;
12911312 if (type_index == expected_type_index ) {
@@ -1319,6 +1340,10 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
13191340 };
13201341}
13211342
1343+ fn isEmpty (comptime T : type ) bool {
1344+ return @typeInfo (T ) != .@"opaque" and @sizeOf (T ) == 0 ;
1345+ }
1346+
13221347// Responsible for calling Zig functions from JS invokations. This could
13231348// probably just contained in Executor, but having this specific logic, which
13241349// is somewhat repetitive between constructors, functions, getters, etc contained
@@ -1380,8 +1405,7 @@ fn Caller(comptime E: type) type {
13801405 comptime assertSelfReceiver (named_function );
13811406
13821407 var args = try self .getArgs (named_function , 1 , info );
1383- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1384- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1408+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
13851409
13861410 // inject 'self' as the first parameter
13871411 @field (args , "0" ) = zig_instance ;
@@ -1402,8 +1426,7 @@ fn Caller(comptime E: type) type {
14021426 switch (arg_fields .len ) {
14031427 0 = > {}, // getters _can_ be parameterless
14041428 1 , 2 = > {
1405- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1406- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1429+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
14071430 comptime assertSelfReceiver (named_function );
14081431 @field (args , "0" ) = zig_instance ;
14091432 if (comptime arg_fields .len == 2 ) {
@@ -1421,8 +1444,7 @@ fn Caller(comptime E: type) type {
14211444 const S = named_function .S ;
14221445 comptime assertSelfReceiver (named_function );
14231446
1424- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1425- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1447+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
14261448
14271449 const Setter = @TypeOf (named_function .func );
14281450 var args : ParamterTypes (Setter ) = undefined ;
@@ -1464,8 +1486,7 @@ fn Caller(comptime E: type) type {
14641486 switch (arg_fields .len ) {
14651487 0 , 1 , 2 = > @compileError (named_function .full_name ++ " must take at least a u32 and *bool parameter" ),
14661488 3 , 4 = > {
1467- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1468- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1489+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
14691490 comptime assertSelfReceiver (named_function );
14701491 @field (args , "0" ) = zig_instance ;
14711492 @field (args , "1" ) = idx ;
@@ -1492,8 +1513,7 @@ fn Caller(comptime E: type) type {
14921513 const S = named_function .S ;
14931514 comptime assertSelfReceiver (named_function );
14941515
1495- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1496- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1516+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
14971517
14981518 const IndexedSet = @TypeOf (named_function .func );
14991519 var args : ParamterTypes (IndexedSet ) = undefined ;
@@ -1537,8 +1557,7 @@ fn Caller(comptime E: type) type {
15371557 switch (arg_fields .len ) {
15381558 0 , 1 , 2 = > @compileError (named_function .full_name ++ " must take at least a u32 and *bool parameter" ),
15391559 3 , 4 = > {
1540- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1541- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1560+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
15421561 comptime assertSelfReceiver (named_function );
15431562 @field (args , "0" ) = zig_instance ;
15441563 @field (args , "1" ) = try self .nameToString (name );
@@ -1565,8 +1584,7 @@ fn Caller(comptime E: type) type {
15651584 const S = named_function .S ;
15661585 comptime assertSelfReceiver (named_function );
15671586
1568- const external = info .getThis ().getInternalField (0 ).castTo (v8 .External );
1569- const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), external .get ());
1587+ const zig_instance = try E .typeTaggedAnyOpaque (named_function , * Receiver (S ), info .getThis ());
15701588
15711589 const IndexedSet = @TypeOf (named_function .func );
15721590 var args : ParamterTypes (IndexedSet ) = undefined ;
@@ -1749,14 +1767,17 @@ fn Caller(comptime E: type) type {
17491767 const corresponding_js_index = last_parameter_index - adjusted_offset ;
17501768 const corresponding_js_value = info .getArg (@as (u32 , @intCast (corresponding_js_index )));
17511769 if (corresponding_js_value .isArray () == false and slice_type != u8 ) {
1752- const arr = try self .call_allocator .alloc (last_parameter_type_info .pointer .child , js_parameter_count - expected_js_parameters + 1 );
1753- for (arr , corresponding_js_index .. ) | * a , i | {
1754- const js_value = info .getArg (@as (u32 , @intCast (i )));
1755- a .* = try self .jsValueToZig (named_function , slice_type , js_value );
1756- }
1757-
17581770 is_variadic = true ;
1759- @field (args , tupleFieldName (last_parameter_index )) = arr ;
1771+ if (js_parameter_count == 0 ) {
1772+ @field (args , tupleFieldName (last_parameter_index )) = &.{};
1773+ } else {
1774+ const arr = try self .call_allocator .alloc (last_parameter_type_info .pointer .child , js_parameter_count - expected_js_parameters + 1 );
1775+ for (arr , corresponding_js_index .. ) | * a , i | {
1776+ const js_value = info .getArg (@as (u32 , @intCast (i )));
1777+ a .* = try self .jsValueToZig (named_function , slice_type , js_value );
1778+ }
1779+ @field (args , tupleFieldName (last_parameter_index )) = arr ;
1780+ }
17601781 }
17611782 }
17621783 }
@@ -1773,7 +1794,7 @@ fn Caller(comptime E: type) type {
17731794 @compileError ("State must be the 2nd parameter: " ++ named_function .full_name );
17741795 } else if (i >= js_parameter_count ) {
17751796 if (@typeInfo (param .type .? ) != .optional ) {
1776- return error .TypeError ;
1797+ return error .InvalidArgument ;
17771798 }
17781799 @field (args , tupleFieldName (field_index )) = null ;
17791800 } else {
@@ -1812,7 +1833,7 @@ fn Caller(comptime E: type) type {
18121833 if (obj .internalFieldCount () == 0 ) {
18131834 return error .InvalidArgument ;
18141835 }
1815- return E .typeTaggedAnyOpaque (named_function , * Receiver (ptr .child ), obj . getInternalField ( 0 ). castTo ( v8 . External ). get () );
1836+ return E .typeTaggedAnyOpaque (named_function , * Receiver (ptr .child ), obj );
18161837 }
18171838 },
18181839 .slice = > {
@@ -2271,3 +2292,8 @@ fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque {
22712292 const external_data = obj .getInternalField (0 ).castTo (v8 .External ).get ().? ;
22722293 return @alignCast (@ptrCast (external_data ));
22732294}
2295+
2296+ test {
2297+ std .testing .refAllDecls (@import ("test_primitive_types.zig" ));
2298+ std .testing .refAllDecls (@import ("test_complex_types.zig" ));
2299+ }
0 commit comments