Odin icon indicating copy to clipboard operation
Odin copied to clipboard

"Maybe" assignment failing on struct initilization

Open xzores opened this issue 1 year ago • 6 comments

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.

xzores avatar Jul 21 '24 20:07 xzores

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?

laytan avatar Jul 21 '24 22:07 laytan

You will find it in regui/GUI.odin and then in the init function.

xzores avatar Jul 21 '24 22:07 xzores

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

laytan avatar Jul 21 '24 22:07 laytan

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.

laytan avatar Jul 22 '24 01:07 laytan

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,
}

xzores avatar Jul 22 '24 08:07 xzores

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.

laytan avatar Jul 22 '24 11:07 laytan

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
}

AL1L avatar Nov 25 '24 23:11 AL1L

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

Memresable avatar Dec 09 '24 11:12 Memresable

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

Stannic-7863 avatar Aug 02 '25 05:08 Stannic-7863