Bug Report
Compiling a program that passes a block to OptionParser#on inside a case … when branch matching uninstantiated generic types crashes with BUG: no target defs during LLVM codegen.
The crash happens when all of these conditions are met:
- An abstract base class with Char?, String getters and an abstract method
- Two generic subclasses that are never instantiated anywhere in the program
- A case statement on the base type with when GENERIC1, GENERIC2 (both uninstantiated)
- Inside that branch, a block passed to OptionParser#on that calls a method on the narrowed variable
- Local variables extracted from the parameter before the case are referenced inside the block
- The method containing the case is reached through an array iteration, preventing compile-time type narrowing
The error goes away if at least one of the generic types in the when clause is instantiated somewhere in the program.
Reproduction:
require "option_parser"
abstract class Flag
getter short : Char?
getter long : String
getter description : String
def initialize(@short : Char?, @long : String, @description = "")
end
abstract def parse(value : String) : Nil
end
class SimpleFlag < Flag
def parse(value : String) : Nil; end
end
class TypedFlag(T) < Flag
def parse(value : String) : Nil; end
end
class EnumFlag(T) < Flag
def parse(value : String) : Nil; end
end
class Registry
@items = [] of Flag
def add(short : Char?, long : String)
@items << SimpleFlag.new(short, long); self
end
def run
parser = OptionParser.new
@items.each { |item| register(parser, item) }
parser.parse([] of String)
end
private def register(parser : OptionParser, flag : Flag)
short = flag.short
long = flag.long
case flag
when EnumFlag, TypedFlag
parser.on("-#{short}", "--#{long}=VALUE", flag.description) { |val| flag.parse(val) }
end
end
end
Registry.new.add('v', "verbose").run
Workaround:
Both workarounds work. They just attack the bug differently:
- Force instantiation - makes the compiler see concrete instantiations of the generic types so it can build the dispatch table:
_ = EnumFlag(String).new(nil, "")
_ = TypedFlag(String).new(nil, "")
- Split when clause — avoids the union entirely so no generic dispatch table is needed:
when EnumFlag
...
when TypedFlag
...
Crystal Version
Crystal 1.20.2 (2026-05-15)
LLVM: 22.1.6
Default target: aarch64-apple-darwin25.5.0
Bug Report
Compiling a program that passes a block to OptionParser#on inside a case … when branch matching uninstantiated generic types crashes with BUG: no target defs during LLVM codegen.
The crash happens when all of these conditions are met:
The error goes away if at least one of the generic types in the when clause is instantiated somewhere in the program.
Reproduction:
Workaround:
Both workarounds work. They just attack the bug differently:
Crystal Version