Bug: unions aren't initialized properly when 'using'ed

Bug #1:

package repro

import "core:fmt"

main :: proc() {
  Render_Options :: struct {
    color0: [4]f32,
    color1: Maybe([4]f32),
  }
  Button_Options :: struct {
    using render: Render_Options,
  }

  bo0 := Button_Options{color0 = [4]f32{1, 0, 1, 1}, color1 = [4]f32{0, 0, 1, 1}}
  fmt.println(bo0) // prints: Button_Options{render = Render_Options{color0 = [1, 0, 1, 1], color1 = nil}}
  
  // if we explicitly set the values via 'render', then it works
  bo1 := Button_Options{render = {color0 = [4]f32{1, 0, 1, 1}, color1 = [4]f32{0, 0, 1, 1}}}
  fmt.println(bo1) // prints: Button_Options{render = Render_Options{color0 = [1, 0, 1, 1], color1 = [0, 0, 1, 1]}}
}

Bug #2 (core:fmt bug? or bad codegen?):

package repro

import "core:fmt"

main :: proc() {
  Render_Options :: struct {
    color0: [4]f32,
    color1: union{[4]f32, int}, // NOTE: now a union of two types
  }
  Button_Options :: struct {
    using render: Render_Options,
  }

  ro0 := Render_Options{color0 = [4]f32{1, 0, 1, 1}, color1 = [4]f32{0, 0, 1, 1}}
  fmt.println(ro0) // this is fine

  // bo0 := Button_Options{render = {color0 = [4]f32{1, 0, 1, 1}, color1 = [4]f32{0, 0, 1, 1}}}
  // fmt.println(bo0)
  // ^^^^ If this is uncommented, then the assertion from printing bo1 doesn't happen!
  // ^^^^ If this is uncommented, but we didn't assign values via 'render = ' then the crash still happens

  bo1 := Button_Options{color0 = [4]f32{1, 0, 1, 1}, color1 = [4]f32{0, 0, 1, 1}}
  fmt.println(bo1) // Triggers assertion: core/fmt/fmt.odin(2764:23) Index 140720308486143 is out of range 0..<2
  // OR sometimes: core/fmt/fmt.odin|2754 col 2 r| tag >= 0
}

PS: Actually, bug #2 looks like some kind of UB, but I will leave my ‘guesses’ intact.
PSS: ah, I see #1 was already reported: Issue · GitHub.

Odin version: dev-2026-06:4ad085b30 (this is ‘origin master’ at the time of writing this post, but this happened on older version as well)

@gingerBill: I hope I’m not getting ahead of myself, but I see you marked this as fixed. Single type unions indeed work now but on my machine (tried with LLVM 18.1.8, 20.1.8, and 22.1.6) unions of more than one type are still borked:

package repro

import "core:fmt"

main :: proc() {
  Render_Options :: struct {
    color1: union{[4]f32, int},
  }
  Button_Options :: struct {
    using render: Render_Options,
  }

  bo1 := Button_Options{color1 = [4]f32{25, 91, 13, 255}}
  fmt.println(bo1)
  // == Button_Options{render = Render_Options{color1 = [25, 4.5916e-41, 91, 3.0796e-41]}}
}

In fact, they’re now borked even without being 'using’ed:

package repro

import "core:fmt"

main :: proc() {
  Render_Options :: struct {
    color1: union{[4]f32, int},
  }
  Button_Options :: struct {
    render: Render_Options,
  }

  bo1 := Button_Options{render={color1 = [4]f32{25, 91, 13, 255}}}
  // == Button_Options{render = Render_Options{color1 = [25, 4.5916e-41, 91, 3.079e-41]}}
  fmt.println(bo1)
}

Well… fuck.

I’m working on trying to fix it all.

4 Likes

I’ve fixed this problem now, for the time being. Goddamn is this annoying.

2 Likes

@gingerBill: some bad business with that LLVM fella, he keeps breaking our heckin’ unions. :frowning:

This hangs the compiler (origin master):

package repro

import "core:fmt"

main :: proc() {
  button(options = {margin = [2]f32{10, 10}})
  // if assigned via layout = {margin = .. }} then the compiler doesn't hang
}

button :: proc(options := Widget_Options{}) {
  fmt.println(options)
}

Widget_Options :: struct {
  using layout: Layout_Options,
}

Layout_Options :: struct {
  layout_rect: Maybe([4]f32),

  width,   width_min,  width_max: Maybe(f32),  // if these two lines are commented out, the compiler manages to compile it
  height, height_min, height_max: Maybe(f32), //  ...

  margin:  Maybe([2]f32),
  padding: Maybe([2]f32),
}

It’s funny (or sad for who’s going to fix that! :D) that four out of five times I’ve hit compiler bugs, it’s been because of unions.

Excellent, it looks it has been fixed. Though I’m unsure in which commit.