From 95f01afc59048be2e3723aab2e3f2c120227c263 Mon Sep 17 00:00:00 2001 From: Funakoshi Minto Date: Thu, 7 May 2026 18:23:01 +0900 Subject: [PATCH 1/3] Fix forward reference to Range in Hover causing load-time crash Spoom::LSP::Hover at line 16 declared const :range, T.nilable(Range), but Spoom::LSP::Range was defined later in the same file at line 73. At class-body evaluation time, Range resolved via lexical scope to Ruby's built-in ::Range, which sorbet-runtime auto-coerces to a T::Types::TypedRange. Computing the union hash later raised: Invalid value for type constraint. ... Got a NilClass. (T::Types::TypedRange#name -> T::Utils.coerce on nil element type) This made require 'spoom/sorbet/lsp' crash on load, which in turn broke 'tapioca init' and any CLI entry that touched Spoom::LSP. Fix: reorder so primitive structs (Position, Range, Location) are defined before composites that reference them (Hover). No behavior change, no type-info loss. --- lib/spoom/sorbet/lsp/structures.rb | 58 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/spoom/sorbet/lsp/structures.rb b/lib/spoom/sorbet/lsp/structures.rb index 35717359..c407d01f 100644 --- a/lib/spoom/sorbet/lsp/structures.rb +++ b/lib/spoom/sorbet/lsp/structures.rb @@ -13,35 +13,6 @@ module PrintableSymbol def accept_printer(printer) = raise NotImplementedError, "Abstract method called" end - class Hover < T::Struct - include PrintableSymbol - - const :contents, String - const :range, T.nilable(Range) - - class << self - #: (Hash[untyped, untyped] json) -> Hover - def from_json(json) - Hover.new( - contents: json["contents"]["value"], - range: json["range"] ? Range.from_json(json["range"]) : nil, - ) - end - end - - # @override - #: (SymbolPrinter printer) -> void - def accept_printer(printer) - printer.print("#{contents}\n") - printer.print_object(range) if range - end - - #: -> String - def to_s - "#{contents} (#{range})." - end - end - class Position < T::Struct include PrintableSymbol @@ -100,6 +71,35 @@ def to_s end end + class Hover < T::Struct + include PrintableSymbol + + const :contents, String + const :range, T.nilable(Range) + + class << self + #: (Hash[untyped, untyped] json) -> Hover + def from_json(json) + Hover.new( + contents: json["contents"]["value"], + range: json["range"] ? Range.from_json(json["range"]) : nil, + ) + end + end + + # @override + #: (SymbolPrinter printer) -> void + def accept_printer(printer) + printer.print("#{contents}\n") + printer.print_object(range) if range + end + + #: -> String + def to_s + "#{contents} (#{range})." + end + end + class Location < T::Struct include PrintableSymbol From 3fac52e653afc49539025551f9a9a98250f39ef2 Mon Sep 17 00:00:00 2001 From: Funakoshi Minto Date: Thu, 7 May 2026 19:20:11 +0900 Subject: [PATCH 2/3] Re-trigger CI after CLA signing From 2788f1178efdc0ff3d12541e6571be4e6f75e4d5 Mon Sep 17 00:00:00 2001 From: Funakoshi Minto Date: Fri, 8 May 2026 09:52:07 +0900 Subject: [PATCH 3/3] Update spoom.rbi via `spoom srb sigs export` Co-Authored-By: Claude Opus 4.7 (1M context) --- rbi/spoom.rbi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbi/spoom.rbi b/rbi/spoom.rbi index 10d9b27c..ea216e3d 100644 --- a/rbi/spoom.rbi +++ b/rbi/spoom.rbi @@ -1836,7 +1836,7 @@ class Spoom::LSP::Hover < ::T::Struct include ::Spoom::LSP::PrintableSymbol const :contents, ::String - const :range, T.nilable(T::Range[T.untyped]) + const :range, T.nilable(::Spoom::LSP::Range) sig { override.params(printer: ::Spoom::LSP::SymbolPrinter).void } def accept_printer(printer); end