Class: RuboCop::Cop::Sorbet::RedundantTLet

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
ConstantScope, SignatureHelp, TLetCorrection, TargetSorbetVersion
Defined in:
lib/rubocop/cop/sorbet/redundant_t_let.rb

Overview

Prevents unnecessary T.let where Sorbet infers the type automatically.

When a signature parameter is assigned to an instance variable in initialize, the type is inferred from the signature.

When a constant is assigned a constructor call (.new), optionally followed by .freeze (Sorbet 0.6.13304+), the type is inferred from the class being instantiated. Generic classes (e.g. Set) are excluded: Sorbet infers their constructor calls as applied types like T::Set[T.untyped], so an annotation is still required.

Examples:


# bad
sig { params(a: Integer) }
def initialize(a)
  @a = T.let(a, Integer)
end

# good
sig { params(a: Integer) }
def initialize(a)
  @a = a
end

# good
sig { params(a: Integer) }
def initialize(a)
  @a = T.let(a, T.any(Integer, String))
end

# bad
DEFAULT_PATH = T.let(Pathname.new("/usr/local").freeze, Pathname)

# good
DEFAULT_PATH = Pathname.new("/usr/local").freeze

# good — generic classes are not inferred, so T.let is required
LICENSES = T.let(Set.new(["mit"]).freeze, T::Set[String])

# good — instance variables are only inferred from signature parameters
@path = T.let(Pathname.new("/usr/local"), Pathname)

Constant Summary collapse

MSG =
"Unnecessary T.let. The instance variable type is inferred from the signature."
MSG_CONSTRUCTOR =
"Unnecessary T.let. The constant type is inferred from the constructor."
GENERIC_CLASSES =

Classes whose constructor calls Sorbet infers as applied generic types (e.g. T::Set[T.untyped]) rather than the bare class, so constants assigned them still need an explicit annotation.

["Array", "Class", "Enumerator", "Hash", "Module", "Range", "Set"].freeze

Instance Method Summary collapse

Methods included from TargetSorbetVersion

#enabled_for_sorbet_static_version?, included, #read_sorbet_static_version_from_bundler_lock_file, #sorbet_enabled?, #target_sorbet_static_version_from_bundler_lock_file

Methods included from SignatureHelp

#bare_sig?, #on_block, #on_signature, #sig_with_runtime?, #sig_without_runtime?, #signature?

Instance Method Details

#on_casgn(node) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rubocop/cop/sorbet/redundant_t_let.rb', line 99

def on_casgn(node)
  return unless enabled_for_sorbet_static_version?
  return unless statically_scoped?(node)

  t_let_casgn(node) do |tlet_node, value_node, type_node|
    next unless type_node.const_type?

    constructor = constructor_call(value_node)
    next unless constructor

    class_path = constructor.receiver.source.delete_prefix("::")
    next if GENERIC_CLASSES.include?(class_path)
    next unless class_path == type_node.source.delete_prefix("::")

    add_offense(tlet_node, message: MSG_CONSTRUCTOR) do |corrector|
      replace_t_let(corrector, tlet_node, value_node)
    end
  end
end

#on_def(node) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/rubocop/cop/sorbet/redundant_t_let.rb', line 80

def on_def(node)
  return unless node.method?(:initialize)

  method_args = named_argument_types(node)
  return if method_args.none?

  sig_node = find_sig_node(node)
  return unless sig_node

  sig_params = sig_params(sig_node)&.to_h { |pair| [pair.key.value, pair.value] }
  return unless sig_params&.any?

  ivar_assignments(node).each do |ivasgn_node|
    t_let(ivasgn_node) do |tlet_node, tlet_key, tlet_value|
      find_redundant_t_let(tlet_node, tlet_key, tlet_value, sig_params, method_args)
    end
  end
end

#sig_params(node) ⇒ Object



78
# File 'lib/rubocop/cop/sorbet/redundant_t_let.rb', line 78

def_node_matcher :sig_params, "`(send nil? :params (hash $...))"

#t_let(node) ⇒ Object



72
# File 'lib/rubocop/cop/sorbet/redundant_t_let.rb', line 72

def_node_matcher :t_let, "(ivasgn _ $(send (const {nil? cbase} :T) :let (lvar $_) $_))"

#t_let_casgn(node) ⇒ Object



75
# File 'lib/rubocop/cop/sorbet/redundant_t_let.rb', line 75

def_node_matcher :t_let_casgn, "(casgn _ _ $(send (const {nil? cbase} :T) :let $_ $_))"