Odin
Odin copied to clipboard
"Maybe" assignment failing on struct initilization
Context
This was talked about at: https://discord.com/channels/568138951836172421/568871298428698645/1264674940611858482
Observe the following code:
state = {
active_elements = make(map[Element]Element_container),
gui_pipeline = render.pipeline_make(render.get_default_shader(), .blend, false, false),
default_style = {
default = default_appearance,
hover = default_appearance,
active = default_appearance,
},
}
fmt.printf("default_style : %#v\n", state.default_style);
assert(state.default_style.hover != nil); //This assertion fails.
The assertion failes. Now observe the following code:
state = {
active_elements = make(map[Element]Element_container),
gui_pipeline = render.pipeline_make(render.get_default_shader(), .blend, false, false),
default_style = {
default = default_appearance,
hover = default_appearance,
active = default_appearance,
},
}
fmt.printf("default_style : %#v\n", state.default_style);
state.default_style.hover = default_appearance; //This makes the bug go away.
assert(state.default_style.hover != nil); //This assertion no longer fails.
The assertion no longer failes.
Odin version: Odin: dev-2024-07-nightly:b4ca044 OS: Windows 11 Home Basic (version: 23H2), build 22631.3880 CPU: 13th Gen Intel(R) Core(TM) i9-13900HX RAM: 32507 MiB Backend: LLVM 17.0.1
Expected Behavior
I would expect that the first assertion did not fail.
Current Behavior
The first assertions fails.
Failure Information (for bugs)
I think this is a bug with "Maybe", it seems to not get assigned.
Steps to Reproduce
I have not been able to reproduce this is a simple setup. See "things tested" for more info. To find a example which failes go to: https://github.com/xzores/furbs/tree/Bug_found Download and Run the project, it should fail.
things tested
I have tested the following which DID work as expected:
Temp :: struct {
a : int,
b : Maybe(int),
}
t : Temp = {
a = 5,
b = 5,
}
assert(t.b == 5, "b was not 5");
I have also tried:
Temp3 :: struct {
e : int,
}
Temp2 :: struct {
c : int,
d : Maybe(Temp3),
}
Temp :: struct {
a : int,
b : Maybe(Temp2),
}
t : Temp = {
a = 5,
b = Temp2{
5,
Temp3{
5,
},
},
}
assert(t.b != nil, "failed");
if t2, ok := t.b.?; ok {
assert(t.b != nil, "failed2");
if t3, ok := t2.d.?; ok {
assert(t3.e == 5, "failed3");
}
}
I have also tried:
Temp4 :: struct {
e : int,
}
Temp3 :: union {
Temp4,
}
Temp2 :: struct {
c : int,
d : Maybe(Temp3),
}
Temp :: struct {
a : int,
b : Maybe(Temp2),
}
t : Temp = {
a = 5,
b = Temp2{
5,
Temp4{
5,
},
},
}
assert(t.b != nil, "failed");
if t2, ok := t.b.?; ok {
assert(t.b != nil, "failed2");
if t3, ok := t2.d.?; ok {
assert(t3.(Temp4).e == 5, "failed3");
}
}
ALL OF THIS PASSED, so I have been unable to replicate the bug in a simpler setup.
Can you give us some code that triggers the assert in your linked repo, like a main proc that calls the procedure that asserts in it?
You will find it in regui/GUI.odin and then in the init function.
Yes but just putting a main proc that imports and calls that doesn't work and fails with errors about a missing package ex_defs
Ok got it down to this repro:
package bug
Bar :: union {
int,
}
Foo :: union {
Bar,
}
FooBar :: struct {
foo: Foo,
}
main :: proc() {
foo := FooBar {
foo = 69, // Make this `Bar(69)` and it fixes it.
}
assert(foo.foo != nil)
}
It has to do with this hack: https://github.com/odin-lang/Odin/blob/68550cf9156934f1748fc96af21cfbc30f853e6c/src/llvm_backend_expr.cpp#L4738
Adding a panic at types.cpp:3005 confirms the issue: GB_PANIC("type %s is not a variant of the union %s!\n", type_to_string(v), type_to_string(u));
This hack evidently does not work with nested unions.
I ran out of time to think of a fix (would probably be some kind of recursive/looping solution to account for multiple levels) so I am leaving this info here.
Hmm, I don't experience this, as you can see in my project it seems to work fine for the double union (the "default" struct member), but it does not work for the Maybe and then double union (The "hover" and "active" struct memebers.)
Here is the struct for clarification:
Style :: struct {
default : Appearance, //Works
hover : Maybe(Appearance), //Does not work
active : Maybe(Appearance), //Does not work
}
Appearance :: union {
Colored_appearance,
Patched_appearance,
Textured_appearance,
}
Don't experience what? Remember a Maybe is just a union under the hood. And this also only happens with compound literals with inferred types. I am 99% sure this is your bug.
Not sure if it's related or not, but I have this code that is seemingly a bug
package main
import "core:fmt"
baseline :: proc() {
StructA :: struct {
x: int,
y: StructB,
z: UnionA,
}
UnionA :: union #no_nil {
StructB,
StructC
}
StructB :: struct {
w: int,
}
StructC :: struct {
w: int,
}
data := StructA {
x = 1,
y = StructB{w = 2},
z = StructB{w = 2},
}
assert(data.x == 1)
assert(data.y.w == 2)
assert(data.z.(StructB).w == 2)
}
maybeA :: proc() {
StructA :: struct {
x: int,
y: Maybe(StructB),
z: Maybe(UnionA),
}
UnionA :: union #no_nil {
StructB,
StructC
}
StructB :: struct {
w: int,
}
StructC :: struct {
w: int,
}
data := StructA {
x = 1,
y = StructB{w = 2},
}
// Setting .z after
data.z = StructB {
w = 3,
}
assert(data.x == 1)
assert(data.y.?.w == 2)
assert(data.z != nil)
assert(data.z.?.(StructB).w == 3) // all good :)
}
maybeB :: proc() {
StructA :: struct {
x: int,
y: Maybe(StructB),
z: Maybe(UnionA),
}
UnionA :: union #no_nil {
StructB,
StructC
}
StructB :: struct {
w: int,
}
StructC :: struct {
w: int,
}
data := StructA {
x = 1,
y = StructB{w = 2},
// setting .z inline
z = StructB{w = 3},
}
assert(data.x == 1)
assert(data.y.?.w == 2)
assert(data.z != nil) // this fails
assert(data.z.?.(StructB).w == 3) // doesnt reach
}
main :: proc() {
baseline()
fmt.printfln("baseline: ok")
maybeA()
fmt.printfln("maybeA: ok")
maybeB()
fmt.printfln("maybeB: ok") // does not print
}
i'm not sure but i've probably just hit a bug that's similar to this issue
here's the code:
some_struct :: struct
{
some_var: Maybe([3]f32),
}
sm_var: some_struct
main :: proc()
{
some_const := f32(10.0)
sm_var = some_struct {
some_var = some_const,
}
fmt.println(sm_var.some_var) // nil
sm_var = some_struct {
some_var = 10.0,
}
fmt.println(sm_var.some_var) // [10, 10, 10]
}
for whatever reason if i assign a literal like 10.0 directly to some_var member in the struct initializer it works just fine, but once you assign a variable like some_const into it then it doesn't seem to assign it at all and stays null
package main
import "core:fmt"
Bar :: [4]u8
Foo :: struct {
e: Maybe([4]Bar),
}
main :: proc() {
foo := Foo {
e = Bar{200, 100, 200, 100},
}
fmt.println(foo) // Foo{e = nil}
foo = Foo {
e = 100,
}
fmt.println(foo) // Foo{e = [[100, 100, 100, 100], [100, 100, 100, 100], [100, 100, 100, 100], [100, 100, 100, 100]]}
var: u8 = 69
const: u8 : 42
foo = Foo {
e = var,
}
fmt.println(foo) // Foo{e = nil}
foo = Foo {
e = const,
}
fmt.println(foo) // Foo{e = nil}
foo.e = var
fmt.println(foo) // Foo{e = [[69, 69, 69, 69], [69, 69, 69, 69], [69, 69, 69, 69], [69, 69, 69, 69]]}
foo.e = const
fmt.println(foo) // Foo{e = [[42, 42, 42, 42], [42, 42, 42, 42], [42, 42, 42, 42], [42, 42, 42, 42]]}
}
Adding my example to this issue as well