Search code examples
rubysorbet

How can I resolve sorbet error: "Use of undeclared variable"?


I am experimenting with adding sorbet type information to my gem, pdf-reader. I don't want sorbet to be a runtime dependency for the gem, so all type annotations are in an external file in the rbi/ directory. I also can't extend T::Sig in my classes.

I'd like to enable typed: strict in some files, but doing so flags that I'm using some instance variables without type annotations:

./lib/pdf/reader/rectangle.rb:94: Use of undeclared variable @bottom_left https://srb.help/6002
    94 |        @bottom_left = PDF::Reader::Point.new(
                ^^^^^^^^^^^^

./lib/pdf/reader/rectangle.rb:98: Use of undeclared variable @bottom_right https://srb.help/6002
    98 |        @bottom_right = PDF::Reader::Point.new(
                ^^^^^^^^^^^^^

./lib/pdf/reader/rectangle.rb:102: Use of undeclared variable @top_left https://srb.help/6002
     102 |        @top_left = PDF::Reader::Point.new(
                  ^^^^^^^^^

./lib/pdf/reader/rectangle.rb:106: Use of undeclared variable @top_right https://srb.help/6002
     106 |        @top_right = PDF::Reader::Point.new(

The proposed fix is to use T.let():

@top_right = T.let(PDF::Reader::Point.new(0,0), PDF::Reader::Point)

However I can't do that because it requires a runtime dependency on sorbet.

Is it possible to record annotations for instance variables in an rbi file?


Solution

  • According to the documentation "The syntax of RBI files is the same as normal Ruby files, except that method definitions do not need implementations." So, the syntax for declaring the type of an instance variable in an RBI file is the same as in a Ruby file:

    sig do
      params(
        x1: Numeric,
        y1: Numeric,
        x2: Numeric,
        y2: Numeric
      ).void
    end
    def initialize(x1, y1, x2, y2)
      @top_right = T.let(PDF::Reader::Point.new(0,0), PDF::Reader::Point)
      # …
    end
    

    An alternative would be to use RBS syntax instead of RBI syntax, which supports type annotations for instance variables natively. However, I have found conflicting information on RBS support in Sorbet. There are claims online that Sorbet supports RBS. OTOH, the Sorbet FAQ talks about RBS support in the future tense. On the other other hand, the "future" the FAQ talks about is the release of Ruby 3, which is actually one year in the past.

    In RBS, it would look something like this:

    module PDF
      class Reader
        @top_left: Numeric
        @top_right: Numeric
        @bottom_left: Numeric
        @bottom_right: Numeric
    
        class Rectangle
          def initialize: (
            x1: Numeric,
            y1: Numeric,
            x2: Numeric,
            y2: Numeric
          ) -> void
        end
      end
    end
    

    Or, since they are also attr_readers, it might be enough to just do

    module PDF
      class Reader
        attr_reader top_left: Numeric
        attr_reader top_right: Numeric
        attr_reader bottom_left: Numeric
        attr_reader bottom_right: Numeric
    
        class Rectangle
          def initialize: (
            x1: Numeric,
            y1: Numeric,
            x2: Numeric,
            y2: Numeric
          ) -> void
        end
      end
    end
    

    I believe, this will also implicitly type the corresponding instance variables, but I have not tested this.