From a57b0f1417215b11b0beda93b20bd387c78d4aa1 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sat, 20 Jun 2026 22:34:27 +1000 Subject: [PATCH] Require `tuple` before `env` in the prelude (#8771) `{"a", "b"}.as Enumerable(String)` crashed codegen with `Enumerable::NotFoundError`: `Tuple(String, String)` was missing from `Enumerable(String)`'s including types. The cause is require ordering. `Tuple` does `include Indexable(Union(*T))` (which transitively includes `Enumerable`/`Iterable`), but `tuple` was required after `env`. `ENV` does `extend Enumerable({String, String})`, which instantiates `Tuple(String, String)` before `Tuple` gained those module parents, so its `after_initialize` registered nothing and it never appeared in `Enumerable(String).including_types`. Codegen then failed to find it when upcasting. Other arities worked because they were instantiated later. Requiring `tuple` (and `named_tuple`) in the ordered block right after `indexable` ensures the includes are processed before any early instantiation, so all arities register correctly. This replaces the need for any compiler-side workaround. A codegen spec covers the original reproducer. --- spec/compiler/codegen/cast_spec.cr | 11 +++++++++++ src/prelude.cr | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/compiler/codegen/cast_spec.cr b/spec/compiler/codegen/cast_spec.cr index cbfba96ff70e..9dcf115329ea 100644 --- a/spec/compiler/codegen/cast_spec.cr +++ b/spec/compiler/codegen/cast_spec.cr @@ -497,4 +497,15 @@ describe "Code gen: cast" do Base.as(Base | Base.class).as?(Base | Impl).nil? CRYSTAL end + + it "casts same-type tuple to Enumerable whose splat collapses (#8771)" do + # `Tuple(String, String)` is instantiated early (via `ENV`) before + # `Tuple` includes `Indexable`, so it used to be missing from + # `Enumerable(String).including_types`, crashing this upcast. + run(<<-CRYSTAL).to_string.should eq("ab") + require "prelude" + + ({"a", "b"}.as(Enumerable(String))).to_a.join + CRYSTAL + end end diff --git a/src/prelude.cr b/src/prelude.cr index 267bb7eb1f48..c5beefe4a8aa 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -18,6 +18,13 @@ require "iterable" require "iterator" require "steppable" require "indexable" +# `Tuple` includes `Indexable(Union(*T))`. It must be required here, before any +# file that can register a tuple as an including type of a module (e.g. `env`'s +# `extend Enumerable(...)`), otherwise that early instantiation runs with an +# incomplete parent list and is missing from the module's including types, +# breaking `as Enumerable(T)` upcasts (#8771). +require "tuple" +require "named_tuple" require "string" require "number" require "primitives" @@ -53,7 +60,6 @@ require "io" require "kernel" require "math/math" require "mutex" -require "named_tuple" require "nil" require "humanize" require "path" @@ -78,7 +84,6 @@ require "symbol" require "system" require "crystal/system/thread" require "time" -require "tuple" require "unicode" require "union" require "va_list"