Method: Parlour::TypeParser#parse_node_to_type

Defined in:
lib/parlour/type_parser.rb

#parse_node_to_type(node) ⇒ Parlour::Types::Type

Given an AST node representing an RBI type (such as ‘T::Array’), parses it into a generic type.

Parameters:

  • node (Parser::AST::Node)

Returns:



793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
# File 'lib/parlour/type_parser.rb', line 793

def parse_node_to_type(node)
  case node.type
  when :send
    target, message, *args = *node

    # Special case: is this a generic type instantiation?
    if message == :[]
      names = constant_names(target)
      known_single_element_collections = [:Array, :Set, :Range, :Enumerator, :Enumerable]

      if names.length == 2 && names[0] == :T &&
        known_single_element_collections.include?(names[1])

        parse_err "no type in T::#{names[1]}[...]", node if args.nil? || args.empty?
        parse_err "too many types in T::#{names[1]}[...]", node unless args.length == 1
        return T.must(Types.const_get(T.must(names[1]))).new(parse_node_to_type(T.must(args.first)))
      elsif names.length == 2 && names == [:T, :Hash]
        parse_err "not enough types in T::Hash[...]", node if args.nil? || args.length < 2
        parse_err "too many types in T::Hash[...]", node unless args.length == 2
        return Types::Hash.new(
          parse_node_to_type(args[0]), parse_node_to_type(args[1])
        )
      else
        type = names.join('::')
        if args.nil?
          parse_err(
            "user defined generic '#{type}' requires at least one type parameter",
            node
          )
        end
        return Types::Generic.new(
          type,
          args.map { |arg| parse_node_to_type(arg) }
        )
      end
    end

    # Special case: is this a proc?
    # This parsing is pretty simplified, but you'd also have to be doing
    # something pretty cursed with procs to break this
    # This checks for (send (send (send (const nil :T) :proc) ...) ...)
    # That's the right amount of nesting for T.proc.params(...).returns(...)
    if node.to_a[0].type == :send &&
      node.to_a[0].to_a[0].type == :send &&
      node.to_a[0].to_a[0].to_a[1] == :proc &&
      node.to_a[0].to_a[0].to_a[0].type == :const &&
      node.to_a[0].to_a[0].to_a[0].to_a == [nil, :T] # yuck

      # Get parameters
      params_send = node.to_a[0]
      parse_err "expected 'params' to follow 'T.proc'", node unless params_send.to_a[1] == :params
      parse_err "expected 'params' to have kwargs", node unless params_send.to_a[2].type == :hash

      parameters = params_send.to_a[2].to_a.map do |pair|
        name, value = *pair
        parse_err "expected 'params' name to be symbol", node unless name.type == :sym
        name = name.to_a[0].to_s
        value = parse_node_to_type(value)

        RbiGenerator::Parameter.new(name, type: value)
      end

      # Get return value
      if node.to_a[1] == :void
        return_type = nil
      else
        _, call, *args = *node
        parse_err 'expected .returns or .void', node unless call == :returns
        parse_err 'no argument to .returns', node if args.nil? || args.empty?
        parse_err 'too many arguments to .returns', node unless args.length == 1
        return_type = parse_node_to_type(T.must(args.first))
      end

      return Types::Proc.new(parameters, return_type)
    end

    # The other options for a valid call are all "T.something" methods
    parse_err "unexpected call #{node_to_s(node).inspect} in type", node \
      unless target.type == :const && target.to_a == [nil, :T]

    case message
    when :nilable
      parse_err 'no argument to T.nilable', node if args.nil? || args.empty?
      parse_err 'too many arguments to T.nilable', node unless args.length == 1
      Types::Nilable.new(parse_node_to_type(T.must(args.first)))
    when :any
      Types::Union.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
    when :all
      Types::Intersection.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
    when :let
      # Not really allowed in a type signature, but handy for generalizing
      # constant types
      parse_err 'not enough argument to T.let', node if args.nil? || args.length < 2
      parse_err 'too many arguments to T.nilable', node unless args.length == 2
      parse_node_to_type(args[1])
    when :type_parameter
      parse_err 'no argument to T.type_parameter', node if args.nil? || args.empty?
      parse_err 'too many arguments to T.type_parameter', node unless args.length == 1
      parse_err 'expected T.type_parameter to be passed a symbol', node unless T.must(args.first).type == :sym
      Types::Raw.new(T.must(args.first.to_a[0].to_s))
    when :class_of
      parse_err 'no argument to T.class_of', node if args.nil? || args.empty?
      parse_err 'too many arguments to T.class_of', node unless args.length == 1
      Types::Class.new(parse_node_to_type(args[0]))
    when :untyped
      parse_err 'T.untyped does not accept arguments', node if !args.nil? && !args.empty?
      Types::Untyped.new
    else
      warning "unknown method T.#{message}, treating as untyped", node
      Types::Untyped.new
    end
  when :const
    # Special case: T::Boolean
    if constant_names(node) == [:T, :Boolean]
      return Types::Boolean.new
    end

    # Otherwise, just a plain old constant
    Types::Raw.new(constant_names(node).join('::'))
  when :array
    # Tuple
    Types::Tuple.new(node.to_a.map { |x| parse_node_to_type(T.must(x)) })
  when :hash
    # Shape/record
    keys_to_types = node.to_a.map do |pair|
      key, value = *pair
      parse_err "all shape keys must be symbols", node unless key.type == :sym
      [key.to_a[0], parse_node_to_type(value)]
    end.to_h

    Types::Record.new(keys_to_types)
  else
    parse_err "unable to parse type #{node_to_s(node).inspect}", node
  end
end