How would you do rose memoization with Sorbet?
Asked Answered
A

1

6

Trying to annotate this code, the rose memoization (@||=) gives me an error Use of undeclared variable @git_sha.

# typed: strict
# frozen_string_literal: true

module Util
  extend T::Sig

  sig { returns(String) }
  def self.git_sha
    @git_sha ||= ENV.fetch(
      'GIT_REV',
      `git rev-parse --verify HEAD 2>&1`
    ).chomp
  end
end

As far as I've found, I should declare the variable's type with T.let but haven't figured out specifically how.

Apterous answered 20/6, 2019 at 20:19 Comment(1)
strict mode does not support ivars declared outside of a constructor. Changing the mode to true will make the error go away.Leibniz
O
10

Sorbet now has built-in support for this, as of 0.5.10210. Before that, there were other workarounds (see below).

  1. Initialize the instance variable as T.nilable, and replace all direct access of the instance variable elsewhere with the method:

    # typed: strict
    # frozen_string_literal: true
    
    module Util
      extend T::Sig
    
      sig { returns(String) }
      def self.git_sha
        @git_sha ||= T.let(ENV.fetch(
          'GIT_REV',
          `git rev-parse --verify HEAD 2>&1`
        ).chomp, T.nilable(String))
      end
    end
    

    → View on sorbet.run

    This is the the preferred solution.

  2. Initialize the instance variable outside of the method, and give it a type annotation:

    # typed: strict
    # frozen_string_literal: true
    
    module Util
      extend T::Sig
    
      @git_sha = T.let(nil, T.nilable(String))
    
      sig { returns(String) }
      def self.git_sha
        @git_sha ||= ENV.fetch(
          'GIT_REV',
          `git rev-parse --verify HEAD 2>&1`
        ).chomp
      end
    end
    

    → View on sorbet.run

    Conceptually, there are two phases of execution for this class: when it's initialized, and when it's used. If an instance variable is not given a type annotation when it is initialized in Sorbet, it will be T.untyped everywhere (or an error in # typed: strict). Because if it's not annotated in the initialize, Sorbet can't know which code path might write into this location first. (Even in this case where there is one location, Sorbet doesn't do that sort of global analysis.)

    Sorbet only relaxes this when the instance variable is nilable, in which case it can be initialized anywhere, because Sorbet doesn't need to guarantee that it's initialized as non-nil.

  3. Use a different strictness level.

    Docs on strictness levels.

    If you find it too burdensome to add a type annotation, you can opt out of requiring a type annotation by using # typed: true, where the error requiring type annotations for instance variables is silenced.

Ovoviviparous answered 20/6, 2019 at 22:10 Comment(1)
Hey Jez! Could you please update this answer in light of github.com/sorbet/sorbet/issues/995#issuecomment-1544908063 ? I can also edit it myself, but I'd like your blessing first, since it's be a pretty substantial change to the "meat" of the answerHyperostosis

© 2022 - 2024 — McMap. All rights reserved.