class Parlour::Conversion::RbiToRbs

Converts RBI types to RBS types.

Attributes

rbs_gen[R]

Public Class Methods

new(rbs_gen) click to toggle source
Calls superclass method Parlour::Conversion::Converter::new
# File lib/parlour/conversion/rbi_to_rbs.rb, line 9
def initialize(rbs_gen)
  super()
  @rbs_gen = rbs_gen
end

Public Instance Methods

convert_all(from, to) click to toggle source
# File lib/parlour/conversion/rbi_to_rbs.rb, line 18
def convert_all(from, to)
  from.children.each do |child|
    convert_object(child, to)
  end        
end
convert_object(node, new_parent) click to toggle source
# File lib/parlour/conversion/rbi_to_rbs.rb, line 30
def convert_object(node, new_parent)        
  case node
  when RbiGenerator::StructClassNamespace
    add_warning 'performing a one-way conversion of an RBI struct to RBS', node

    klass = new_parent.create_class(node.name)
    klass.add_comments(node.comments)

    # Create a constructor
    klass.create_method('initialize', [
      RbsGenerator::MethodSignature.new(
        node.props.map do |prop|
          RbsGenerator::Parameter.new(
            "#{prop.name}:",
            type: prop.type,
            required: !prop.optional,
          )
        end,
        nil,
      )
    ])

    # Make each prop a getter (and setter, if not immutable) attribute
    node.props.each do |prop|
      klass.create_attribute(
        prop.name,
        kind: prop.immutable ? :reader : :accessor,
        type: prop.type,
      )
    end

    klass 

  when RbiGenerator::EnumClassNamespace
    add_warning 'performing a one-way conversion of an RBI enum to RBS', node

    klass = new_parent.create_class(node.name)
    klass.add_comments(node.comments)

    # Define .values
    klass.create_method('values', [
      RbsGenerator::MethodSignature.new([], Types::Array.new(node.name))
    ], class_method: true)

    # Define each enum variant
    node.enums.each do |variant|
      # We don't care about any extra value
      variant = variant[0] if Array === variant

      klass.create_constant(variant, type: node.name)
    end

    klass

  when RbiGenerator::Arbitrary
    add_warning 'converting type of Arbitrary is likely to cause syntax errors; doing it anyway', node
    new_parent.create_arbitrary(
      code: node.code,
    ).add_comments(node.comments)

  when RbiGenerator::Attribute
    if node.class_attribute
      add_warning 'RBS does not support class attributes; dropping', node
      return
    end
    new_parent.create_attribute(
      node.name,
      kind: node.kind,
      type: node.type,
    ).add_comments(node.comments)

  when RbiGenerator::ClassNamespace
    if node.abstract
      add_warning 'RBS does not support abstract classes', node
    end
    klass = new_parent.create_class(
      node.name,
      superclass: node.superclass
    )
    klass.add_comments(node.comments)
    node.children.each do |child|
      convert_object(child, klass)
    end

  when RbiGenerator::Constant
    if node.eigen_constant
      add_warning 'RBS does not support constants on eigenclasses; dropping', node
      return
    end
    new_parent.create_constant(
      node.name,
      type: node.value,
    ).add_comments(node.comments)

  when RbiGenerator::TypeAlias
    new_parent.create_type_alias(
      node.name,
      type: node.type,
    ).add_comments(node.comments)

  when RbiGenerator::Extend
    new_parent.create_extend(node.name).add_comments(node.comments)

  when RbiGenerator::Include
    new_parent.create_include(node.name).add_comments(node.comments)

  when RbiGenerator::Method
    # Convert parameters
    parameters = node.parameters
      .reject { |param| param.kind == :block }
      .map do |param|
        RbsGenerator::Parameter.new(
          param.name,
          type: param.type,
          required: param.default.nil?
        )
      end

    # Find block if there is one
    block_param = node.parameters.find { |param| param.kind == :block }
    if block_param
      if String === block_param.type
        add_warning "block must have a Types::Type for conversion; dropping block", node
        block = nil
      else
        # A nilable proc is an optional block
        block_param_type = block_param.type
        if Types::Nilable === block_param_type && Types::Proc === block_param_type.type
          t = T.cast(block_param_type.type, Types::Proc)
          required = false
          block = RbsGenerator::Block.new(t, required)
        elsif Types::Proc === block_param_type
          t = block_param_type
          required = true
          block = RbsGenerator::Block.new(t, required)
        elsif Types::Untyped === block_param_type
          # Consider there to be a block of unknown types
          block = RbsGenerator::Block.new(
            Types::Proc.new(
              [
                Types::Proc::Parameter.new('*args', Types::Untyped.new),
                Types::Proc::Parameter.new('**kwargs', Types::Untyped.new),
              ],
              Types::Untyped.new,
            ),
            false,
          )
        else
          add_warning 'block type must be a Types::Proc (or nilable one); dropping block', node
        end
      end
    else
      block = nil
    end

    new_parent.create_method(
      node.name,
      [
        RbsGenerator::MethodSignature.new(
          parameters,
          node.return_type,
          block: block,
          type_parameters: node.type_parameters,
        )
      ],
      class_method: node.class_method,
    ).add_comments(node.comments)

  when RbiGenerator::ModuleNamespace
    if node.interface
      rbs_node = new_parent.create_interface(
        node.name,
      )
    else
      rbs_node = new_parent.create_module(
        node.name,
      )
    end
    rbs_node.add_comments(node.comments)
    node.children.each do |child|
      convert_object(child, rbs_node)
    end

  when RbiGenerator::Namespace
    add_warning 'unspecialized namespaces are not supposed to be in the tree; you may run into issues', node
    namespace = RbsGenerator::Namespace.new(rbs_gen)
    namespace.add_comments(node.comments)
    node.children.each do |child|
      convert_object(child, namespace)
    end
    new_parent.children << namespace

  else
    raise "missing conversion for #{node.describe}"
    # TODO: stick a T.absurd here
  end
end