ashn-dot-dev / sunder

A modest systems programming language for Unix-like platforms
Apache License 2.0
21 stars 4 forks source link

Cannot use `std::hash_map` and `std::hash_set` with recursive types #127

Open ashn-dot-dev opened 12 months ago

ashn-dot-dev commented 12 months ago

std::hash_map

import "std";

struct foo {
    var map: std::hash_map[[ssize, foo]];
}

func main() void {
    var x: foo = uninit;
}
$ sunder-compile test.sunder
[std/std.sunder:3100] error: struct `struct { var state: usize; var index: usize; var key: ssize; var value: foo; }` contains a member variable of incomplete struct type `foo`
        var value: V;
        ^
[test.sunder:4] info: ...encountered during template instantiation of `std::hash_map[[ssize, foo]]`
    var map: std::hash_map[[ssize, foo]];
                  ^

std::hash_set

import "std";

struct foo {
    var set: std::set[[foo]];
}

func main() void {
    var x: foo = uninit;
}
$ sunder-compile test.sunder
[std/std.sunder:3096] error: struct `struct { var state: usize; var index: usize; var key: foo; var value: void; }` contains a member variable of incomplete struct type `foo`
        var key: K;
        ^
[std/std.sunder:3408] info: ...encountered during template instantiation of `std::hash_map[[foo, void]]`
    var _hash_map: std::hash_map[[T, void]];
                        ^
[test.sunder:4] info: ...encountered during template instantiation of `std::hash_set[[foo]]`
    var set: std::hash_set[[foo]];
                  ^

Additional Notes

Attempting to box the templated type using a pointer also does not work:

import "std";

struct foo {
    var map: *std::hash_map[[ssize, foo]];
}

func main() void {
    var x: foo = uninit;
}
$ sunder-compile test.sunder
[std/std.sunder:3100] error: struct `struct { var state: usize; var index: usize; var key: ssize; var value: foo; }` contains a member variable of incomplete struct type `foo`
        var value: V;
        ^
[test.sunder:4] info: ...encountered during template instantiation of `std::hash_map[[ssize, foo]]`
    var map: *std::hash_map[[ssize, foo]];
                   ^

In order to hold the data, a boxed any must be used to "smuggle" the heap allocated struct:

import "std";
import "sys";

struct foo {
    var _map: *any; # std::hash_map[[ssize, foo]];

    func map(self: *foo) *std::hash_map[[ssize, foo]] {
        return (:*std::hash_map[[ssize, foo]])self.*._map;
    }
}

func main() void {
    var x: foo = uninit;
    sys::dump[[typeof(x.map())]](x.map());
}
$ sunder-run test.sunder
00 00 00 00 00 00 00 00

The discovery of this issue, and an example of this smuggling in action, comes from an initial draft of a data interchange format library called bubby: https://github.com/ashn-dot-dev/bubby/blob/4f1f6f0f605b8ba7f6d18fa1c0945460576fabab/bubby.sunder#L22C1-L23C50

ashn-dot-dev commented 8 months ago

A better workaround was used in https://github.com/ashn-dot-dev/bubby/commit/afc18d96430c4d74059844c00f9a453d83b496a4. The size of both std::hash_map and std::hash_set is fixed regardless of the template types used, so std::hash_map[[void, void]] and std::hash_set[[void]] can be used as placeholder types, with type punning being used to later extract the desired type from the hash map and hash set member variables.