0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SteepコードリーディングAdvent Calendar 2024

Day 9

Steepコードリーディング(9日目)

Last updated at Posted at 2024-12-21

Steepコードリーディング(9日目)

TypeCheckService#.type_checkの続きを見ていきます。
どうもconstruction.synthesize(source.node)で型検査してるっぽいんだよな。

Steep::TypeConstruction

Steep::TypeConstruction#synthesize

Qiitaのエディタが固まってまともに使えなくなるくらい長いです。別記事で1つずつ見ていきます。

def synthesize(node, hint: nil, condition: false)
  Steep.logger.tagged "synthesize:(#{node.location&.yield_self {|loc| loc.expression.to_s.split(/:/, 2).last } || "-"})" do
    Steep.logger.debug node.type
    case node.type
    when :begin, :kwbegin
      yield_self do
        end_pos = node.loc.expression.end_pos

        *mid_nodes, last_node = each_child_node(node).to_a
        if last_node
          pair = mid_nodes.inject(Pair.new(type: AST::Builtin.nil_type, constr: self)) do |pair, node|
            pair.constr.synthesize(node).yield_self {|p| pair + p }.tap do |new_pair|
              if new_pair.constr.context != pair.constr.context
                # update context
                range = node.loc.expression.end_pos..end_pos
                typing.cursor_context.set(range, new_pair.constr.context)
              end
            end
          end

          p = pair.constr.synthesize(last_node, hint: hint, condition: condition)
          last_pair = pair + p
          last_pair.constr.add_typing(node, type: last_pair.type, constr: last_pair.constr)
        else
          add_typing(node, type: AST::Builtin.nil_type)
        end
      end

    when :lvasgn
      yield_self do
        name, rhs = node.children

        case name
        when :_, :__any__
          synthesize(rhs, hint: AST::Builtin.any_type).yield_self do |pair|
            add_typing(node, type: AST::Builtin.any_type, constr: pair.constr)
          end
        when :__skip__
          add_typing(node, type: AST::Builtin.any_type)
        else
          if enforced_type = context.type_env.enforced_type(name)
            case
            when !hint
              hint = enforced_type
            when check_relation(sub_type: enforced_type, super_type: hint).success?
              # enforced_type is compatible with hint and more specific to hint.
              # This typically happens when hint is untyped, top, or void.
              hint = enforced_type
            end
          end

          if rhs
            rhs_type, rhs_constr, rhs_context = synthesize(rhs, hint: hint).to_ary

            constr = rhs_constr.update_type_env do |type_env|
              var_type = rhs_type

              if enforced_type = type_env.enforced_type(name)
                if result = no_subtyping?(sub_type: rhs_type, super_type: enforced_type)
                  typing.add_error(
                    Diagnostic::Ruby::IncompatibleAssignment.new(
                      node: node,
                      lhs_type: enforced_type,
                      rhs_type: rhs_type,
                      result: result
                    )
                  )

                  var_type = enforced_type
                end

                if rhs_type.is_a?(AST::Types::Any)
                  var_type = enforced_type
                end
              end

              type_env.assign_local_variable(name, var_type, enforced_type)
            end

            constr.add_typing(node, type: rhs_type)
          else
            add_typing(node, type: enforced_type || AST::Builtin.any_type)
          end
        end
      end

    when :lvar
      yield_self do
        var = node.children[0]

        if SPECIAL_LVAR_NAMES.include?(var)
          add_typing node, type: AST::Builtin.any_type
        else
          if (type = context.type_env[var])
            add_typing node, type: type
          else
            fallback_to_any(node)
          end
        end
      end

    when :ivasgn
      name = node.children[0]
      rhs = node.children[1]

      rhs_type, constr = synthesize(rhs, hint: context.type_env[name])

      constr.ivasgn(node, rhs_type)

    when :ivar
      yield_self do
        name = node.children[0]

        if type = context.type_env[name]
          add_typing(node, type: type)
        else
          fallback_to_any node
        end
      end

    when :match_with_lvasgn
      each_child_node(node) do |child|
        synthesize(child)
      end
      add_typing(node, type: AST::Builtin.any_type)

    when :op_asgn
      yield_self do
        lhs, op, rhs = node.children

        case lhs.type
        when :lvasgn
          var_node = lhs.updated(:lvar)
          send_node = rhs.updated(:send, [var_node, op, rhs])
          new_node = node.updated(:lvasgn, [lhs.children[0], send_node])

          type, constr = synthesize(new_node, hint: hint)

          constr.add_typing(node, type: type)

        when :ivasgn
          var_node = lhs.updated(:ivar)
          send_node = rhs.updated(:send, [var_node, op, rhs])
          new_node = node.updated(:ivasgn, [lhs.children[0], send_node])

          type, constr = synthesize(new_node, hint: hint)

          constr.add_typing(node, type: type)

        when :cvasgn
          var_node = lhs.updated(:cvar)
          send_node = rhs.updated(:send, [var_node, op, rhs])
          new_node = node.updated(:cvasgn, [lhs.children[0], send_node])

          type, constr = synthesize(new_node, hint: hint)

          constr.add_typing(node, type: type)

        when :gvasgn
          var_node = lhs.updated(:gvar)
          send_node = rhs.updated(:send, [var_node, op, rhs])
          new_node = node.updated(:gvasgn, [lhs.children[0], send_node])

          type, constr = synthesize(new_node, hint: hint)

          constr.add_typing(node, type: type)

        when :send
          new_rhs = rhs.updated(:send, [lhs, node.children[1], node.children[2]])
          new_node = lhs.updated(:send, [lhs.children[0], :"#{lhs.children[1]}=", *lhs.children.drop(2), new_rhs])

          type, constr = synthesize(new_node, hint: hint)

          constr.add_typing(node, type: type)

        else
          Steep.logger.error("Unexpected op_asgn lhs: #{lhs.type}")

          _, constr = synthesize(rhs)
          constr.add_typing(node, type: AST::Builtin.any_type)
        end
      end

    when :super
      yield_self do
        if self_type && method_context!.method
          if super_def = method_context!.super_method
            super_method = Interface::Shape::Entry.new(
              method_name: method_context!.name,
              private_method: true,
              overloads: super_def.defs.map {|type_def|
                type = checker.factory.method_type(type_def.type)
                Interface::Shape::MethodOverload.new(type, [type_def])
              }
            )

            call, constr = type_method_call(
              node,
              receiver_type: self_type,
              method_name: method_context!.name || raise("method context must have a name"),
              method: super_method,
              arguments: node.children,
              block_params: nil,
              block_body: nil,
              tapp: nil,
              hint: hint
            )

            if call && constr
              constr.add_call(call)
            else
              error = Diagnostic::Ruby::UnresolvedOverloading.new(
                node: node,
                receiver_type: self_type,
                method_name: method_context!.name,
                method_types: super_method.method_types
              )
              call = TypeInference::MethodCall::Error.new(
                node: node,
                context: context.call_context,
                method_name: method_context!.name || raise("method context must have a name"),
                receiver_type: self_type,
                errors: [error]
              )

              constr = synthesize_children(node)

              fallback_to_any(node) { error }
            end
          else
            type_check_untyped_args(node.children).fallback_to_any(node) do
              Diagnostic::Ruby::UnexpectedSuper.new(node: node, method: method_context!.name)
            end
          end
        else
          type_check_untyped_args(node.children).fallback_to_any(node)
        end
      end

    when :def
      yield_self do
        # @type var node: Parser::AST::Node & Parser::AST::_DefNode

        name, args_node, body_node = node.children

        with_method_constr(
          name,
          node,
          args: args_node.children,
          self_type: module_context&.instance_type,
          definition: module_context&.instance_definition
        ) do |new|
          # @type var new: TypeConstruction

          new.typing.cursor_context.set_node_context(node, new.context)
          new.typing.cursor_context.set_body_context(node, new.context)

          new.method_context!.tap do |method_context|
            if method_context.method
              if owner = method_context.method.implemented_in || method_context.method.defined_in
                method_name = InstanceMethodName.new(type_name: owner, method_name: name)
                new.typing.source_index.add_definition(method: method_name, definition: node)
              end
            end
          end

          new = new.synthesize_children(args_node)

          body_pair = if body_node
                        return_type = expand_alias(new.method_context!.return_type)
                        if !return_type.is_a?(AST::Types::Void)
                          new.check(body_node, return_type) do |_, actual_type, result|
                            if new.method_context!.attribute_setter?
                              typing.add_error(
                                Diagnostic::Ruby::SetterBodyTypeMismatch.new(
                                  node: node,
                                  expected: new.method_context!.return_type,
                                  actual: actual_type,
                                  result: result,
                                  method_name: new.method_context!.name
                                )
                              )
                            else
                              typing.add_error(
                                Diagnostic::Ruby::MethodBodyTypeMismatch.new(
                                  node: node,
                                  expected: new.method_context!.return_type,
                                  actual: actual_type,
                                  result: result
                                )
                              )
                            end
                          end
                        else
                          new.synthesize(body_node)
                        end
                      else
                        return_type = expand_alias(new.method_context!.return_type)
                        if !return_type.is_a?(AST::Types::Void)
                          result = check_relation(sub_type: AST::Builtin.nil_type, super_type: return_type)
                          if result.failure?
                            if new.method_context!.attribute_setter?
                              typing.add_error(
                                Diagnostic::Ruby::SetterBodyTypeMismatch.new(
                                  node: node,
                                  expected: new.method_context!.return_type,
                                  actual: AST::Builtin.nil_type,
                                  result: result,
                                  method_name: new.method_context!.name
                                )
                              )
                            else
                              typing.add_error(
                                Diagnostic::Ruby::MethodBodyTypeMismatch.new(
                                  node: node,
                                  expected: new.method_context!.return_type,
                                  actual: AST::Builtin.nil_type,
                                  result: result
                                )
                              )
                            end
                          end
                        end

                        Pair.new(type: AST::Builtin.nil_type, constr: new)
                      end

          if body_node
            # Add context to ranges from the end of the method body to the beginning of the `end` keyword
            if node.loc.end
              # Skip end-less def
              begin_pos = body_node.loc.expression.end_pos
              end_pos = node.loc.end.begin_pos
              typing.cursor_context.set(begin_pos..end_pos, body_pair.context)
            end
          end

          if module_context
            module_context.defined_instance_methods << node.children[0]
          end

          add_typing(node, type: AST::Builtin::Symbol.instance_type)
        end
      end

    when :defs
      synthesize(node.children[0]).type.tap do |self_type|
        self_type = expand_self(self_type)
        definition =
          case self_type
          when AST::Types::Name::Instance
            name = self_type.name
            checker.factory.definition_builder.build_instance(name)
          when AST::Types::Name::Singleton
            name = self_type.name
            checker.factory.definition_builder.build_singleton(name)
          end

        args_node = node.children[2]
        new = for_new_method(
          node.children[1],
          node,
          args: args_node.children,
          self_type: self_type,
          definition: definition
        )
        new.typing.cursor_context.set_node_context(node, new.context)
        new.typing.cursor_context.set_body_context(node, new.context)

        new.method_context!.tap do |method_context|
          if method_context.method
            name_ = node.children[1]

            method_name =
              case self_type
              when AST::Types::Name::Instance
                InstanceMethodName.new(type_name: method_context.method.implemented_in || raise, method_name: name_)
              when AST::Types::Name::Singleton
                SingletonMethodName.new(type_name: method_context.method.implemented_in || raise, method_name: name_)
              end

            new.typing.source_index.add_definition(method: method_name, definition: node)
          end
        end

        new = new.synthesize_children(args_node)

        each_child_node(node.children[2]) do |arg|
          new.synthesize(arg)
        end

        if node.children[3]
          return_type = expand_alias(new.method_context!.return_type)
          if !return_type.is_a?(AST::Types::Void)
            new.check(node.children[3], return_type) do |return_type, actual_type, result|
              typing.add_error(
                Diagnostic::Ruby::MethodBodyTypeMismatch.new(
                  node: node,
                  expected: return_type,
                  actual: actual_type,
                  result: result
                )
              )
            end
          else
            new.synthesize(node.children[3])
          end
        end
      end

      if node.children[0].type == :self
        module_context.defined_module_methods << node.children[1]
      end

      add_typing(node, type: AST::Builtin::Symbol.instance_type)

    when :return
      yield_self do
        method_return_type =
          if method_context
            expand_alias(method_context.return_type)
          end

        case node.children.size
        when 0
          value_type = AST::Builtin.nil_type
          constr = self
        when 1
          return_value_node = node.children[0]
          value_type, constr = synthesize(return_value_node, hint: method_return_type)
        else
          # It returns an array
          array = node.updated(:array)
          value_type, constr = synthesize(array, hint: method_return_type)
        end

        if method_return_type
          unless method_context.nil? || method_return_type.is_a?(AST::Types::Void)
            result = constr.check_relation(sub_type: value_type, super_type: method_return_type)

            if result.failure?
              if method_context.attribute_setter?
                typing.add_error(
                  Diagnostic::Ruby::SetterReturnTypeMismatch.new(
                    node: node,
                    method_name: method_context.name,
                    expected: method_return_type,
                    actual: value_type,
                    result: result
                  )
                )
              else
                typing.add_error(
                  Diagnostic::Ruby::ReturnTypeMismatch.new(
                    node: node,
                    expected: method_return_type,
                    actual: value_type,
                    result: result
                  )
                )
              end
            end
          end
        end

        constr.add_typing(node, type: AST::Builtin.bottom_type)
      end

    when :break
      value = node.children[0]

      if break_context
        break_type = break_context.break_type

        if value
          check(value, break_type) do |break_type, actual_type, result|
            typing.add_error(
              Diagnostic::Ruby::BreakTypeMismatch.new(
                node: node,
                expected: break_type,
                actual: actual_type,
                result: result
              )
            )
          end
        else
          unless break_type.is_a?(AST::Types::Bot)
            check_relation(sub_type: AST::Builtin.nil_type, super_type: break_type).else do |result|
              typing.add_error(
                Diagnostic::Ruby::ImplicitBreakValueMismatch.new(
                  node: node,
                  jump_type: break_type,
                  result: result
                )
              )
            end
          end
        end
      else
        synthesize(value) if value
        typing.add_error Diagnostic::Ruby::UnexpectedJump.new(node: node)
      end

      add_typing(node, type: AST::Builtin.bottom_type)

    when :next
      value = node.children[0]

      if break_context
        if next_type = break_context.next_type
          next_type = deep_expand_alias(next_type) || next_type

          if value
            _, constr = check(value, next_type) do |break_type, actual_type, result|
              typing.add_error(
                Diagnostic::Ruby::BreakTypeMismatch.new(
                  node: node,
                  expected: break_type,
                  actual: actual_type,
                  result: result
                )
              )
            end
          else
            check_relation(sub_type: AST::Builtin.nil_type, super_type: next_type).else do |result|
              typing.add_error(
                Diagnostic::Ruby::BreakTypeMismatch.new(
                  node: node,
                  expected: next_type,
                  actual: AST::Builtin.nil_type,
                  result: result
                )
              )
            end
          end
        else
          if value
            synthesize(value)
            typing.add_error Diagnostic::Ruby::UnexpectedJumpValue.new(node: node)
          end
        end
      else
        synthesize(value) if value
        typing.add_error Diagnostic::Ruby::UnexpectedJump.new(node: node)
      end

      add_typing(node, type: AST::Builtin.bottom_type)

    when :retry
      add_typing(node, type: AST::Builtin.bottom_type)

    when :procarg0
      yield_self do
        constr = self #: TypeConstruction

        node.children.each do |arg|
          if arg.is_a?(Symbol)
            if SPECIAL_LVAR_NAMES === arg
              _, constr = add_typing(node, type: AST::Builtin.any_type)
            else
              type = context.type_env[arg]

              if type
                _, constr = add_typing(node, type: type)
              else
                type = AST::Builtin.any_type
                _, constr = lvasgn(node, type)
              end
            end
          else
            _, constr = constr.synthesize(arg)
          end
        end

        Pair.new(constr: constr, type: AST::Builtin.any_type)
      end

    when :mlhs
      yield_self do
        constr = self #: TypeConstruction

        node.children.each do |arg|
          _, constr = constr.synthesize(arg)
        end

        Pair.new(constr: constr, type: AST::Builtin.any_type)
      end

    when :arg, :kwarg
      yield_self do
        var = node.children[0]

        if SPECIAL_LVAR_NAMES.include?(var)
          add_typing(node, type: AST::Builtin.any_type)
        else
          type = context.type_env[var]

          if type
            add_typing(node, type: type)
          else
            type = AST::Builtin.any_type
            lvasgn(node, type)
          end
        end
      end

    when :optarg, :kwoptarg
      yield_self do
        var = node.children[0]
        rhs = node.children[1]

        if SPECIAL_LVAR_NAMES.include?(var)
          synthesize(rhs)
          add_typing(node, type: AST::Builtin.any_type)
        else
          var_type = context.type_env[var]

          if var_type
            type, constr = check(rhs, var_type) do |expected_type, actual_type, result|
              typing.add_error(
                Diagnostic::Ruby::IncompatibleAssignment.new(
                  node: node,
                  lhs_type: expected_type,
                  rhs_type: actual_type,
                  result: result
                )
              )
            end
          else
            type, constr = synthesize(rhs)
          end

          constr.add_typing(node, type: type)
        end
      end

    when :restarg
      yield_self do
        var = node.children[0]

        if SPECIAL_LVAR_NAMES.include?(var)
          return add_typing(node, type: AST::Builtin.any_type)
        end

        type = context.type_env[var]

        unless type
          if context.method_context&.method_type
            Steep.logger.error { "Unknown variable: #{node}" }
          end
          typing.add_error Diagnostic::Ruby::FallbackAny.new(node: node)
          type = AST::Builtin::Array.instance_type(AST::Builtin.any_type)
        end

        add_typing(node, type: type)
      end

    when :kwrestarg
      yield_self do
        var = node.children[0]

        if SPECIAL_LVAR_NAMES.include?(var)
          return add_typing(node, type: AST::Builtin.any_type)
        end

        type = context.type_env[var]
        unless type
          if context.method_context&.method_type
            Steep.logger.error { "Unknown variable: #{node}" }
          end
          typing.add_error Diagnostic::Ruby::FallbackAny.new(node: node)
          type = AST::Builtin::Hash.instance_type(AST::Builtin::Symbol.instance_type, AST::Builtin.any_type)
        end

        add_typing(node, type: type)
      end

    when :float
      add_typing(node, type: AST::Builtin::Float.instance_type)

    when :rational
      add_typing(node, type: AST::Types::Name::Instance.new(name: TypeName("::Rational"), args: []))

    when :complex
      add_typing(node, type: AST::Types::Name::Instance.new(name: TypeName("::Complex"), args: []))

    when :nil
      add_typing(node, type: AST::Builtin.nil_type)

    when :int
      yield_self do
        literal_type = test_literal_type(node.children[0], hint)

        if literal_type
          add_typing(node, type: literal_type)
        else
          add_typing(node, type: AST::Builtin::Integer.instance_type)
        end
      end

    when :sym
      yield_self do
        literal_type = test_literal_type(node.children[0], hint)

        if literal_type
          add_typing(node, type: literal_type)
        else
          add_typing(node, type: AST::Builtin::Symbol.instance_type)
        end
      end

    when :str
      yield_self do
        literal_type = test_literal_type(node.children[0], hint)

        if literal_type
          add_typing(node, type: literal_type)
        else
          add_typing(node, type: AST::Builtin::String.instance_type)
        end
      end

    when :true, :false
      ty = node.type == :true ? AST::Types::Literal.new(value: true) : AST::Types::Literal.new(value: false)

      case
      when hint && check_relation(sub_type: ty, super_type: hint).success? && !hint.is_a?(AST::Types::Any) && !hint.is_a?(AST::Types::Top)
        add_typing(node, type: hint)
      when condition
        add_typing(node, type: ty)
      else
        add_typing(node, type: AST::Types::Boolean.instance)
      end

    when :hash, :kwargs
      # :kwargs happens for method calls with keyword argument, but the method doesn't have keyword params.
      # Conversion from kwargs to hash happens, and this when-clause is to support it.
      type_hash(node, hint: hint).tap do |pair|
        if pair.type == AST::Builtin::Hash.instance_type(fill_untyped: true)
          case hint
          when AST::Types::Any, AST::Types::Top, AST::Types::Void
            # ok
          else
            unless hint == pair.type
              pair.constr.typing.add_error Diagnostic::Ruby::UnannotatedEmptyCollection.new(node: node)
            end
          end
        end
      end

    when :dstr, :xstr
      each_child_node(node) do |child|
        synthesize(child)
      end

      add_typing(node, type: AST::Builtin::String.instance_type)

    when :dsym
      each_child_node(node) do |child|
        synthesize(child)
      end

      add_typing(node, type: AST::Builtin::Symbol.instance_type)

    when :class
      yield_self do
        constr = self

        # @type var name_node: Parser::AST::Node
        # @type var super_node: Parser::AST::Node?

        name_node, super_node, _ = node.children

        if name_node.type == :const
          _, constr, class_name = synthesize_constant_decl(name_node, name_node.children[0], name_node.children[1]) do
            typing.add_error(
              Diagnostic::Ruby::UnknownConstant.new(node: name_node, name: name_node.children[1]).class!
            )
          end
        else
          _, constr = synthesize(name_node)
        end

        if class_name
          typing.source_index.add_definition(constant: class_name, definition: name_node)
        end

        if super_node
          if super_node.type == :const
            _, constr, super_name = constr.synthesize_constant(super_node, super_node.children[0], super_node.children[1]) do
              typing.add_error(
                Diagnostic::Ruby::UnknownConstant.new(node: super_node, name: super_node.children[1]).class!
              )
            end

            if super_name
              typing.source_index.add_reference(constant: super_name, ref: super_node)
            end
          else
            _, constr = synthesize(super_node, hint: nil, condition: false)
          end
        end

        with_class_constr(node, class_name, super_name) do |constructor|
          if module_type = constructor.module_context&.module_type
            _, constructor = constructor.add_typing(name_node, type: module_type)
          else
            _, constructor = constructor.fallback_to_any(name_node)
          end

          constructor.typing.cursor_context.set_node_context(node, constructor.context)
          constructor.typing.cursor_context.set_body_context(node, constructor.context)

          constructor.synthesize(node.children[2]) if node.children[2]

          if constructor.module_context&.implement_name && !namespace_module?(node)
            constructor.validate_method_definitions(node, constructor.module_context.implement_name || raise)
          end
        end

        add_typing(node, type: AST::Builtin.nil_type)
      end

    when :module
      yield_self do
        constr = self

        # @type var name_node: Parser::AST::Node
        name_node, _ = node.children

        if name_node.type == :const
          _, constr, module_name = synthesize_constant_decl(name_node, name_node.children[0], name_node.children[1]) do
            typing.add_error Diagnostic::Ruby::UnknownConstant.new(node: name_node, name: name_node.children[1]).module!
          end
        else
          _, constr = synthesize(name_node)
        end

        if module_name
          constr.typing.source_index.add_definition(constant: module_name, definition: name_node)
        end

        with_module_constr(node, module_name) do |constructor|
          if module_type = constructor.module_context&.module_type
            _, constructor = constructor.add_typing(name_node, type: module_type)
          else
            _, constructor = constructor.fallback_to_any(name_node)
          end

          constructor.typing.cursor_context.set_node_context(node, constructor.context)
          constructor.typing.cursor_context.set_body_context(node, constructor.context)

          constructor.synthesize(node.children[1]) if node.children[1]

          if constructor.module_context&.implement_name && !namespace_module?(node)
            constructor.validate_method_definitions(node, constructor.module_context.implement_name || raise)
          end
        end

        add_typing(node, type: AST::Builtin.nil_type)
      end

    when :sclass
      yield_self do
        type, constr = synthesize(node.children[0]).to_ary

        with_sclass_constr(node, type) do |constructor|
          unless constructor
            typing.add_error(
              Diagnostic::Ruby::UnsupportedSyntax.new(
                node: node,
                message: "sclass receiver must be instance type or singleton type, but type given `#{type}`"
              )
            )
            return constr.add_typing(node, type: AST::Builtin.nil_type)
          end

          constructor.typing.cursor_context.set_node_context(node, constructor.context)
          constructor.typing.cursor_context.set_body_context(node, constructor.context)

          constructor.synthesize(node.children[1]) if node.children[1]

          if constructor.module_context.instance_definition && module_context.module_definition
            if constructor.module_context.instance_definition.type_name == module_context.module_definition.type_name
              module_context.defined_module_methods.merge(constructor.module_context.defined_instance_methods)
            end
          end
        end

        constr.add_typing(node, type: AST::Builtin.nil_type)
      end

    when :self
      add_typing node, type: AST::Types::Self.instance

    when :cbase
      add_typing node, type: AST::Types::Void.instance

    when :const
      yield_self do
        type, constr, name = synthesize_constant(node, node.children[0], node.children[1])

        if name
          typing.source_index.add_reference(constant: name, ref: node)
        end

        Pair.new(type: type, constr: constr)
      end

    when :casgn
      yield_self do
        constant_type, constr, constant_name = synthesize_constant_decl(nil, node.children[0], node.children[1]) do
          typing.add_error(
            Diagnostic::Ruby::UnknownConstant.new(
              node: node,
              name: node.children[1]
            )
          )
        end

        if constant_name
          typing.source_index.add_definition(constant: constant_name, definition: node)
        end

        value_type, constr = constr.synthesize(node.children.last, hint: constant_type)

        result = check_relation(sub_type: value_type, super_type: constant_type)
        if result.failure?
          typing.add_error(
            Diagnostic::Ruby::IncompatibleAssignment.new(
              node: node,
              lhs_type: constant_type,
              rhs_type: value_type,
              result: result
            )
          )

          constr.add_typing(node, type: constant_type)
        else
          constr.add_typing(node, type: value_type)
        end
      end

    when :yield
      if method_context && method_context.method_type
        if block_type = method_context.block_type
          if block_type.type.params
            type = AST::Types::Proc.new(
              type: block_type.type,
              block: nil,
              self_type: block_type.self_type
            )
            args = TypeInference::SendArgs.new(
              node: node,
              arguments: node.children,
              type: type
            )

            # @type var errors: Array[Diagnostic::Ruby::Base]
            errors = []
            constr = type_check_args(
              nil,
              args,
              Subtyping::Constraints.new(unknowns: []),
              errors
            )

            errors.each do |error|
              typing.add_error(error)
            end
          else
            constr = type_check_untyped_args(node.children)
          end

          add_typing(node, type: block_type.type.return_type)
        else
          typing.add_error(Diagnostic::Ruby::UnexpectedYield.new(node: node))
          fallback_to_any node
        end
      else
        fallback_to_any node
      end

    when :zsuper
      yield_self do
        if method_context && method_context.method
          if method_context.super_method
            types = method_context.super_method.method_types.map {|method_type|
              checker.factory.method_type(method_type).type.return_type
            }
            add_typing(node, type: union_type(*types))
          else
            fallback_to_any(node) do
              Diagnostic::Ruby::UnexpectedSuper.new(node: node, method: method_context.name)
            end
          end
        else
          fallback_to_any node
        end
      end

    when :array
      yield_self do
        if node.children.empty?
          if hint
            array = AST::Builtin::Array.instance_type(AST::Builtin.any_type)
            if check_relation(sub_type: array, super_type: hint).success?
              add_typing node, type: hint
            else
              add_typing node, type: array
            end
          else
            typing.add_error Diagnostic::Ruby::UnannotatedEmptyCollection.new(node: node)
            add_typing node, type: AST::Builtin::Array.instance_type(AST::Builtin.any_type)
          end
        else
          if hint
            tuples = select_flatten_types(hint) {|type| type.is_a?(AST::Types::Tuple) } #: Array[AST::Types::Tuple]
            unless tuples.empty?
              tuples.each do |tuple|
                typing.new_child() do |child_typing|
                  if pair = with_new_typing(child_typing).try_tuple_type(node, tuple)
                    return pair.with(constr: pair.constr.save_typing)
                  end
                end
              end
            end
          end

          if hint
            arrays = select_flatten_types(hint) {|type| AST::Builtin::Array.instance_type?(type) } #: Array[AST::Types::Name::Instance]
            unless arrays.empty?
              arrays.each do |array|
                typing.new_child() do |child_typing|
                  pair = with_new_typing(child_typing).try_array_type(node, array)
                  if pair.constr.check_relation(sub_type: pair.type, super_type: hint).success?
                    return pair.with(constr: pair.constr.save_typing)
                  end
                end
              end
            end
          end

          try_array_type(node, nil)
        end
      end

    when :and
      yield_self do
        left_node, right_node = node.children

        left_type, constr, left_context = synthesize(left_node, hint: hint, condition: true).to_ary

        interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: builder_config)
        left_truthy, left_falsy = interpreter.eval(env: left_context.type_env, node: left_node)

        if left_type.is_a?(AST::Types::Logic::Env)
          left_type = left_type.type
        end

        right_type, constr, right_context =
          constr
            .update_type_env { left_truthy.env }
            .tap {|constr| typing.cursor_context.set_node_context(right_node, constr.context) }
            .for_branch(right_node)
            .synthesize(right_node, hint: hint, condition: true).to_ary

        right_truthy, right_falsy = interpreter.eval(env: right_context.type_env, node: right_node)

        case
        when left_truthy.unreachable
          # Always left_falsy
          env = left_falsy.env
          type = left_falsy.type
        when left_falsy.unreachable
          # Always left_truthy ==> right
          env = right_context.type_env
          type = right_type
        when right_truthy.unreachable && right_falsy.unreachable
          env = left_falsy.env
          type = left_falsy.type
        else
          env = context.type_env.join(left_falsy.env, right_context.type_env)
          type = union_type(left_falsy.type, right_type)

          unless type.is_a?(AST::Types::Any)
            if check_relation(sub_type: type, super_type: AST::Types::Boolean.instance).success?
              type = AST::Types::Boolean.instance
            end
          end
        end

        if condition
          type = AST::Types::Logic::Env.new(
            truthy: right_truthy.env,
            falsy: context.type_env.join(left_falsy.env, right_falsy.env),
            type: type
          )
        end

        constr.update_type_env { env }.add_typing(node, type: type)
      end

    when :or
      yield_self do
        left_node, right_node = node.children

        if hint
          left_hint = union_type_unify(hint, AST::Builtin.nil_type, AST::Builtin.false_type)
        end
        left_type, constr, left_context = synthesize(left_node, hint: left_hint, condition: true).to_ary

        interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: builder_config)
        left_truthy, left_falsy = interpreter.eval(env: left_context.type_env, node: left_node)

        if left_type.is_a?(AST::Types::Logic::Env)
          left_type = left_type.type
        end

        right_type, constr, right_context =
          constr
            .update_type_env { left_falsy.env }
            .tap {|constr| typing.cursor_context.set_node_context(right_node, constr.context) }
            .for_branch(right_node)
            .synthesize(right_node, hint: left_truthy.type, condition: true).to_ary

        right_truthy, right_falsy = interpreter.eval(env: right_context.type_env, node: right_node)

        case
        when left_falsy.unreachable
          env = left_truthy.env
          type = left_truthy.type
        when left_truthy.unreachable
          # Always left_falsy ==> right
          env = right_context.type_env
          type = right_type
        when right_truthy.unreachable && right_falsy.unreachable
          env = left_truthy.env
          type = left_truthy.type
        else
          env = context.type_env.join(left_truthy.env, right_context.type_env)
          type = union_type(left_truthy.type, right_type)

          unless type.is_a?(AST::Types::Any)
            if check_relation(sub_type: type, super_type: AST::Types::Boolean.instance).success?
              type = AST::Types::Boolean.instance
            end
          end
        end

        if condition
          type = AST::Types::Logic::Env.new(
            truthy: context.type_env.join(left_truthy.env, right_truthy.env),
            falsy: right_falsy.env,
            type: type
          )
        end

        constr.update_type_env { env }.add_typing(node, type: type)
      end

    when :if
      yield_self do
        cond, true_clause, false_clause = node.children

        cond_type, constr = synthesize(cond, condition: true).to_ary
        interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: constr.typing, config: builder_config)
        truthy, falsy = interpreter.eval(env: constr.context.type_env, node: cond)

        if true_clause
          true_pair =
            constr
              .update_type_env { truthy.env }
              .for_branch(true_clause)
              .tap {|constr| typing.cursor_context.set_node_context(true_clause, constr.context) }
              .synthesize(true_clause, hint: hint)
        end

        if false_clause
          false_pair =
            constr
              .update_type_env { falsy.env }
              .for_branch(false_clause)
              .tap {|constr| typing.cursor_context.set_node_context(false_clause, constr.context) }
              .synthesize(false_clause, hint: hint)
        end

        constr = constr.update_type_env do |env|
          envs = [] #: Array[TypeInference::TypeEnv]

          unless truthy.unreachable
            if true_pair
              unless true_pair.type.is_a?(AST::Types::Bot)
                envs << true_pair.context.type_env
              end
            else
              envs << truthy.env
            end
          end

          if false_pair
            unless falsy.unreachable
              unless false_pair.type.is_a?(AST::Types::Bot)
                envs << false_pair.context.type_env
              end
            end
          else
            envs << falsy.env
          end

          env.join(*envs)
        end

        if truthy.unreachable
          if true_clause
            _, _, _, loc = deconstruct_if_node!(node)

            if loc.respond_to?(:keyword)
              condition_loc = loc #: NodeHelper::condition_loc
              case condition_loc.keyword.source
              when "if", "elsif"
                location = condition_loc.begin || condition_loc.keyword
              when "unless"
                # `else` token always exists
                location = condition_loc.else || raise
              end
            else
              location = true_clause.loc.expression
            end

            typing.add_error(
              Diagnostic::Ruby::UnreachableBranch.new(
                node: true_clause,
                location: location || raise
              )
            )
          end
        end

        if falsy.unreachable
          if false_clause
            _, _, _, loc = deconstruct_if_node!(node)

            if loc.respond_to?(:keyword)
              condition_loc = loc #: NodeHelper::condition_loc

              case condition_loc.keyword.source
              when "if", "elsif"
                # `else` token always exists
                location = condition_loc.else || raise
              when "unless"
                location = condition_loc.begin || condition_loc.keyword
              end
            else
              location = false_clause.loc.expression
            end

            typing.add_error(
              Diagnostic::Ruby::UnreachableBranch.new(
                node: false_clause,
                location: location || raise
              )
            )
          end
        end

        node_type = union_type_unify(true_pair&.type || AST::Builtin.nil_type, false_pair&.type || AST::Builtin.nil_type)
        add_typing(node, type: node_type, constr: constr)
      end

    when :case
      yield_self do
        # @type var node: Parser::AST::Node & Parser::AST::_CaseNode

        cond, *whens, els = node.children

        constr = self #: TypeConstruction
        interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: builder_config)

        if cond
          types, envs = TypeInference::CaseWhen.type_check(constr, node, interpreter, hint: hint, condition: condition)
        else
          branch_results = [] #: Array[Pair]

          condition_constr = constr

          whens.each do |when_clause|
            when_clause_constr = condition_constr
            body_envs = [] #: Array[TypeInference::TypeEnv]

            # @type var tests: Array[Parser::AST::Node]
            # @type var body: Parser::AST::Node?
            *tests, body = when_clause.children

            branch_reachable = false

            tests.each do |test|
              test_type, condition_constr = condition_constr.synthesize(test, condition: true)
              truthy, falsy = interpreter.eval(env: condition_constr.context.type_env, node: test)
              truthy_env = truthy.env
              falsy_env = falsy.env

              condition_constr = condition_constr.update_type_env { falsy_env }
              body_envs << truthy_env

              branch_reachable ||= !truthy.unreachable
            end

            branch_result =
              if body
                when_clause_constr
                  .for_branch(body)
                  .update_type_env {|env| env.join(*body_envs) }
                  .tap {|constr| typing.cursor_context.set_node_context(body, constr.context) }
                  .synthesize(body, hint: hint)
              else
                Pair.new(type: AST::Builtin.nil_type, constr: when_clause_constr)
              end

            branch_results << branch_result

            unless branch_reachable
              unless branch_result.type.is_a?(AST::Types::Bot)
                typing.add_error(
                  Diagnostic::Ruby::UnreachableValueBranch.new(
                    node: when_clause,
                    type: branch_result.type,
                    location: when_clause.location.keyword || raise
                  )
                )
              end
            end
          end

          if els
            branch_results << condition_constr.synthesize(els, hint: hint)
          end

          types = branch_results.map(&:type)
          envs = branch_results.map {|result| result.constr.context.type_env }

          unless els
            types << AST::Builtin.nil_type
          end
        end

        constr = constr.update_type_env do |env|
          env.join(*envs)
        end

        add_typing(node, type: union_type_unify(*types), constr: constr)
      end

    when :rescue
      yield_self do
        body, *resbodies, else_node = node.children
        body_pair = synthesize(body, hint: hint) if body

        # @type var body_constr: TypeConstruction
        body_constr = if body_pair
                        update_type_env do |env|
                          env.join(env, body_pair.context.type_env)
                        end
                      else
                        self
                      end

        resbody_pairs = resbodies.map do |resbody|
          # @type var exn_classes: Parser::AST::Node
          # @type var assignment: Parser::AST::Node?
          # @type var body: Parser::AST::Node?
          exn_classes, assignment, body = resbody.children

          if exn_classes
            case exn_classes.type
            when :array
              exn_types = exn_classes.children.map {|child| synthesize(child).type }
            else
              Steep.logger.error "Unexpected exception list: #{exn_classes.type}"
            end
          end

          if assignment
            case assignment.type
            when :lvasgn
              var_name = assignment.children[0]
            else
              Steep.logger.error "Unexpected rescue variable assignment: #{assignment.type}"
            end
          end

          resbody_construction = body_constr.for_branch(resbody).update_type_env do |env|
            assignments = {} #: Hash[Symbol, AST::Types::t]

            case
            when exn_classes && var_name && exn_types
              instance_types = exn_types.map do |type|
                type = expand_alias(type)
                case
                when type.is_a?(AST::Types::Name::Singleton)
                  to_instance_type(type)
                else
                  AST::Builtin.any_type
                end
              end

              assignments[var_name] = AST::Types::Union.build(types: instance_types)
            when var_name
              assignments[var_name] = AST::Builtin.any_type
            end

            env.assign_local_variables(assignments)
          end

          if body
            resbody_construction.typing.cursor_context.set_node_context(body, resbody_construction.context)
            resbody_construction.synthesize(body, hint: hint)
          else
            Pair.new(constr: body_constr, type: AST::Builtin.nil_type)
          end
        end

        resbody_pairs.select! do |pair|
          no_subtyping?(sub_type: pair.type, super_type: AST::Types::Bot.instance)
        end

        resbody_types = resbody_pairs.map(&:type)
        resbody_envs = resbody_pairs.map {|pair| pair.context.type_env }

        else_constr = body_pair&.constr || self

        if else_node
          else_type, else_constr = else_constr.for_branch(else_node).synthesize(else_node, hint: hint)
          else_constr
            .update_type_env {|env| env.join(*resbody_envs, env) }
            .add_typing(node, type: union_type(else_type, *resbody_types))
        else
          if resbody_types.empty?
            constr = body_pair ? body_pair.constr : self
            constr.add_typing(node, type: body_pair&.type || AST::Builtin.nil_type)
          else
            update_type_env {|env| env.join(*resbody_envs, else_constr.context.type_env) }
              .add_typing(node, type: union_type(*[body_pair&.type, *resbody_types].compact))
          end
        end
      end

    when :resbody
      yield_self do
        klasses, asgn, body = node.children
        synthesize(klasses) if klasses
        synthesize(asgn) if asgn
        if body
          body_type = synthesize(body, hint: hint).type
          add_typing(node, type: body_type)
        else
          add_typing(node, type: AST::Builtin.nil_type)
        end
      end

    when :ensure
      yield_self do
        body, ensure_body = node.children
        body_type = synthesize(body).type if body
        synthesize(ensure_body) if ensure_body
        if body_type
          add_typing(node, type: body_type)
        else
          add_typing(node, type: AST::Builtin.nil_type)
        end
      end

    when :masgn
      type_masgn(node)

    when :for
      yield_self do
        asgn, collection, body = node.children

        collection_type, constr, collection_context = synthesize(collection).to_ary
        collection_type = expand_self(collection_type)

        if collection_type.is_a?(AST::Types::Any)
          var_type = AST::Builtin.any_type
        else
          if each = calculate_interface(collection_type, :each, private: true)
            method_type = (each.method_types || []).find do |type|
              if type.block
                if type.block.type.params
                  type.block.type.params.first_param
                else
                  true
                end
              end
            end
            if method_type
              if block = method_type.block
                if first_param = block.type&.params&.first_param
                  var_type = first_param.type #: AST::Types::t
                else
                  var_type = AST::Builtin.any_type
                end
              end
            end
          end
        end
        var_name = asgn.children[0] #: Symbol

        if var_type
          if body
            body_constr = constr.update_type_env do |type_env|
              type_env = type_env.assign_local_variables({ var_name => var_type })
              pins = type_env.pin_local_variables(nil)
              type_env.merge(local_variable_types: pins)
            end

            typing.cursor_context.set_body_context(node, body_constr.context)
            _, _, body_context = body_constr.synthesize(body).to_ary

            constr = constr.update_type_env do |env|
              env.join(collection_context.type_env, body_context.type_env)
            end
          else
            constr = self
          end

          add_typing(node, type: collection_type, constr: constr)
        else
          constr = synthesize_children(node, skips: [collection])

          constr.fallback_to_any(node) do
            Diagnostic::Ruby::NoMethod.new(
              node: node,
              method: :each,
              type: collection_type
            )
          end
        end
      end
    when :while, :until
      yield_self do
        cond, body = node.children
        cond_type, constr = synthesize(cond, condition: true).to_ary

        interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: builder_config)
        truthy, falsy = interpreter.eval(env: constr.context.type_env, node: cond)
        truthy_env = truthy.env
        falsy_env = falsy.env

        case node.type
        when :while
          body_env, exit_env = truthy_env, falsy_env
        when :until
          exit_env, body_env = truthy_env, falsy_env
        else
          raise
        end

        body_env or raise
        exit_env or raise


        if body
          pins = body_env.pin_local_variables(nil)
          body_env = body_env.merge(local_variable_types: pins)

          _, body_constr =
            constr
              .update_type_env { body_env }
              .for_branch(body, break_context: TypeInference::Context::BreakContext.new(break_type: hint || AST::Builtin.nil_type, next_type: nil))
              .tap {|constr| typing.cursor_context.set_node_context(body, constr.context) }
              .synthesize(body).to_ary

          constr = constr.update_type_env {|env| env.join(exit_env, body_constr.context.type_env) }
        else
          constr = constr.update_type_env { exit_env }
        end

        add_typing(node, type: AST::Builtin.nil_type, constr: constr)
      end

    when :while_post, :until_post
      yield_self do
        cond, body = node.children

        _, cond_constr, = synthesize(cond)

        if body
          for_loop =
            cond_constr
              .update_type_env {|env| env.merge(local_variable_types: env.pin_local_variables(nil)) }
              .for_branch(body, break_context: TypeInference::Context::BreakContext.new(break_type: hint || AST::Builtin.nil_type, next_type: nil))

          typing.cursor_context.set_node_context(body, for_loop.context)
          _, body_constr, body_context = for_loop.synthesize(body)

          constr = cond_constr.update_type_env {|env| env.join(env, body_context.type_env) }

          add_typing(node, type: AST::Builtin.nil_type, constr: constr)
        else
          add_typing(node, type: AST::Builtin.nil_type, constr: cond_constr)
        end
      end

    when :irange, :erange
      begin_node, end_node = node.children

      constr = self
      begin_type, constr = if begin_node
                             constr.synthesize(begin_node).to_ary
                           else
                             [AST::Builtin.nil_type, constr]
                           end
      end_type, constr = if end_node
                           constr.synthesize(end_node).to_ary
                         else
                           [AST::Builtin.nil_type, constr]
                         end

      type = AST::Builtin::Range.instance_type(union_type(begin_type, end_type))
      add_typing(node, type: type, constr: constr)

    when :regexp
      each_child_node(node) do |child|
        synthesize(child)
      end

      add_typing(node, type: AST::Builtin::Regexp.instance_type)

    when :regopt
      # ignore
      add_typing(node, type: AST::Builtin.any_type)

    when :nth_ref
      add_typing(node, type: union_type(AST::Builtin::String.instance_type, AST::Builtin.nil_type))

    when :back_ref
      synthesize(node.updated(:gvar), hint: hint)

    when :or_asgn, :and_asgn
      yield_self do
        asgn, rhs = node.children

        case asgn.type
        when :lvasgn
          type, constr = synthesize(rhs, hint: hint)
          constr.lvasgn(asgn, type)
        when :ivasgn
          type, constr = synthesize(rhs, hint: hint)
          constr.ivasgn(asgn, type)
        when :gvasgn
          type, constr = synthesize(rhs, hint: hint)
          constr.gvasgn(asgn, type)
        when :send
          children = asgn.children.dup
          children[1] = :"#{children[1]}="
          send_arg_nodes = [*children, rhs]
          rhs_ = node.updated(:send, send_arg_nodes)
          node_type = case node.type
                      when :or_asgn
                        :or
                      when :and_asgn
                        :and
                      end
          node_ = node.updated(node_type, [asgn, rhs_])

          synthesize(node_, hint: hint)
        else
          Steep.logger.error { "#{node.type} with #{asgn.type} lhs is not supported"}
          fallback_to_any(node)
        end
      end

    when :defined?
      type_any_rec(node, only_children: true)
      add_typing(node, type: AST::Builtin.optional(AST::Builtin::String.instance_type))

    when :gvasgn
      yield_self do
        name, rhs = node.children
        lhs_type = context.type_env[name]
        rhs_type, constr = synthesize(rhs, hint: lhs_type).to_ary

        type, constr = constr.gvasgn(node, rhs_type)

        constr.add_typing(node, type: type)
      end

    when :gvar
      yield_self do
        name = node.children.first
        if type = context.type_env[name]
          add_typing(node, type: type)
        else
          fallback_to_any(node) do
            Diagnostic::Ruby::UnknownGlobalVariable.new(node: node, name: name)
          end
        end
      end

    when :block_pass
      yield_self do
        value_node = node.children[0]

        constr = self #: TypeConstruction

        if value_node
          type, constr = synthesize(value_node, hint: hint)

          if hint.is_a?(AST::Types::Proc) && value_node.type == :sym
            if hint.one_arg?
              if hint.type.params
                # Assumes Symbol#to_proc implementation
                param_type = hint.type.params.required[0]
                case param_type
                when AST::Types::Any
                  type = AST::Types::Any.instance
                else
                  if method = calculate_interface(param_type, private: true)&.methods&.[](value_node.children[0])
                    return_types = method.method_types.filter_map do |method_type|
                      if method_type.type.params.nil? || method_type.type.params.optional?
                        method_type.type.return_type
                      end
                    end

                    unless return_types.empty?
                      type = AST::Types::Proc.new(
                        type: Interface::Function.new(
                          params: Interface::Function::Params.empty.with_first_param(
                            Interface::Function::Params::PositionalParams::Required.new(param_type)
                          ),
                          return_type: return_types[0],
                          location: nil
                        ),
                        block: nil,
                        self_type: nil
                      )
                    end
                  end
                end
              end
            else
              Steep.logger.error "Passing multiple args through Symbol#to_proc is not supported yet"
            end
          end

          case
          when type.is_a?(AST::Types::Proc)
            # nop
          when AST::Builtin::Proc.instance_type?(type)
            # nop
          else
            type = try_convert(type, :to_proc) || type
          end
        else
          # Anonymous block_pass only happens inside method definition
          if block_type = method_context!.block_type
            type = AST::Types::Proc.new(
              type: block_type.type,
              block: nil,
              self_type: block_type.self_type
            )

            if block_type.optional?
              type = union_type(type, AST::Builtin.nil_type)
            end
          else
            type = AST::Builtin.nil_type
          end
        end

        add_typing node, type: type
      end

    when :blockarg
      yield_self do
        each_child_node node do |child|
          synthesize(child)
        end

        add_typing node, type: AST::Builtin.any_type
      end

    when :cvasgn
      name, rhs = node.children

      type, constr = synthesize(rhs, hint: hint)

      var_type =
        if class_vars = module_context.class_variables
          if ty = class_vars[name]
            checker.factory.type(ty)
          end
        end

      if var_type
        result = constr.check_relation(sub_type: type, super_type: var_type)

        if result.success?
          add_typing node, type: type, constr: constr
        else
          fallback_to_any node do
            Diagnostic::Ruby::IncompatibleAssignment.new(
              node: node,
              lhs_type: var_type,
              rhs_type: type,
              result: result
            )
          end
        end
      else
        fallback_to_any(node)
      end

    when :cvar
      name = node.children[0] #: Symbol
      var_type =
        if cvs = module_context.class_variables
          if ty = cvs[name]
            checker.factory.type(ty)
          end
        end

      if var_type
        add_typing node, type: var_type
      else
        fallback_to_any node
      end

    when :alias
      add_typing node, type: AST::Builtin.nil_type

    when :splat
      yield_self do
        typing.add_error(
          Diagnostic::Ruby::UnsupportedSyntax.new(
            node: node,
            message: "Unsupported splat node occurrence"
          )
        )

        each_child_node node do |child|
          synthesize(child)
        end

        add_typing node, type: AST::Builtin.any_type
      end

    when :args
      constr = self #: TypeConstruction

      each_child_node(node) do |child|
        _, constr = constr.synthesize(child)
      end

      add_typing node, type: AST::Builtin.any_type, constr: constr

    when :assertion
      yield_self do
        # @type var as_type: AST::Node::TypeAssertion
        asserted_node, as_type = node.children

        type = as_type.type(module_context.nesting, checker, [])

        case type
        when Array
          type.each do |error|
            typing.add_error(
              Diagnostic::Ruby::RBSError.new(
                error: error,
                node: node,
                location: error.location || raise
              )
            )
          end

          synthesize(asserted_node, hint: hint)

        when nil, RBS::ParsingError
          synthesize(asserted_node, hint: hint)

        else
          actual_type, constr = synthesize(asserted_node, hint: type)

          if no_subtyping?(sub_type: type, super_type: actual_type) && no_subtyping?(sub_type: actual_type, super_type: type)
            typing.add_error(
              Diagnostic::Ruby::FalseAssertion.new(
                node: node,
                assertion_type: type,
                node_type: actual_type
              )
            )
          end

          constr.add_typing(node, type: type)
        end
      end

    when :tapp
      yield_self do
        # @type var tapp: AST::Node::TypeApplication
        sendish, tapp = node.children

        if (array = tapp.types(module_context.nesting, checker, [])).is_a?(Enumerator)
          array.each do |error|
            typing.add_error(
              Diagnostic::Ruby::RBSError.new(
                error: error,
                node: node,
                location: error.location || raise
              )
            )
          end
        end

        type, constr = synthesize_sendish(sendish, hint: hint, tapp: tapp)

        constr.add_typing(node, type: type)
      end

    when :block, :numblock, :send, :csend
      synthesize_sendish(node, hint: hint, tapp: nil)

    when :forwarded_args, :forward_arg
      add_typing(node, type: AST::Builtin.any_type)

    else
      typing.add_error(Diagnostic::Ruby::UnsupportedSyntax.new(node: node))
      add_typing(node, type: AST::Builtin.any_type)

    end.tap do |pair|
      unless pair.is_a?(Pair) && !pair.type.is_a?(Pair)
        # Steep.logger.error { "result = #{pair.inspect}" }
        # Steep.logger.error { "node = #{node.type}" }
        raise "#synthesize should return an instance of Pair: #{pair.class}, node=#{node.inspect}"
      end
    end
  rescue RBS::BaseError => exn
    Steep.logger.warn("hello")
    Steep.logger.warn { "Unexpected RBS error: #{exn.message}" }
    exn.backtrace&.each {|loc| Steep.logger.warn "  #{loc}" }
    typing.add_error(Diagnostic::Ruby::UnexpectedError.new(node: node, error: exn))
    type_any_rec(node)
  rescue StandardError => exn
    Steep.log_error exn
    typing.add_error(Diagnostic::Ruby::UnexpectedError.new(node: node, error: exn))
    type_any_rec(node)
  end
end
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?