Class: RBI::Type

Inherits:
Object
  • Object
show all
Extended by:
T::Helpers, T::Sig
Defined in:
lib/rbi/type.rb,
lib/rbi/rbs_printer.rb,
lib/rbi/type_parser.rb,
lib/rbi/type_visitor.rb

Overview

The base class for all RBI types.

Defined Under Namespace

Classes: All, Any, Anything, AttachedClass, Boolean, Class, ClassOf, Composite, Error, Generic, Nilable, NoReturn, Proc, SelfType, Shape, Simple, Tuple, TypeParameter, Untyped, Visitor, Void

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeType

: -> void



695
696
697
# File 'lib/rbi/type.rb', line 695

def initialize
  @nilable = T.let(false, T::Boolean)
end

Class Method Details

.all(type1, type2, *types) ⇒ Object

Builds a type that represents an intersection of multiple types like ‘T.all(String, Integer)`.

Note that this method transforms types such as ‘T.all(String, String)` into `String`, so it may return something other than a `All`. : (Type type1, Type type2, *Type types) -> Type



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/rbi/type.rb', line 559

def all(type1, type2, *types)
  types = [type1, type2, *types]

  # TODO: should we move this logic to a `flatten!`, `normalize!` or `simplify!` method?
  flattened = types.flatten.flat_map do |type|
    case type
    when All
      type.types
    else
      type
    end
  end.uniq

  if flattened.size == 1
    T.must(flattened.first)
  else
    raise ArgumentError, "RBI::Type.all should have at least 2 types supplied" if flattened.size < 2

    All.new(flattened)
  end
end

.any(type1, type2, *types) ⇒ Object

Builds a type that represents a union of multiple types like ‘T.any(String, Integer)`.

Note that this method transforms types such as ‘T.any(String, NilClass)` into `T.nilable(String)`, so it may return something other than a `Any`. : (Type type1, Type type2, *Type types) -> Type



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/rbi/type.rb', line 586

def any(type1, type2, *types)
  types = [type1, type2, *types]

  # TODO: should we move this logic to a `flatten!`, `normalize!` or `simplify!` method?
  flattened = types.flatten.flat_map do |type|
    case type
    when Any
      type.types
    else
      type
    end
  end

  is_nilable = T.let(false, T::Boolean)

  types = flattened.filter_map do |type|
    case type
    when Simple
      if type.name == "NilClass"
        is_nilable = true
        nil
      else
        type
      end
    when Nilable
      is_nilable = true
      type.type
    else
      type
    end
  end.uniq

  has_true_class = types.any? { |type| type.is_a?(Simple) && type.name == "TrueClass" }
  has_false_class = types.any? { |type| type.is_a?(Simple) && type.name == "FalseClass" }

  if has_true_class && has_false_class
    types = types.reject { |type| type.is_a?(Simple) && (type.name == "TrueClass" || type.name == "FalseClass") }
    types << boolean
  end

  type = case types.size
  when 0
    if is_nilable
      is_nilable = false
      simple("NilClass")
    else
      raise ArgumentError, "RBI::Type.any should have at least 2 types supplied"
    end
  when 1
    T.must(types.first)
  else
    Any.new(types)
  end

  if is_nilable
    nilable(type)
  else
    type
  end
end

.anythingObject

Builds a type that represents ‘T.anything`. : -> Anything



484
485
486
# File 'lib/rbi/type.rb', line 484

def anything
  Anything.new
end

.attached_classObject

Builds a type that represents ‘T.attached_class`. : -> AttachedClass



490
491
492
# File 'lib/rbi/type.rb', line 490

def attached_class
  AttachedClass.new
end

.booleanObject

Builds a type that represents ‘T::Boolean`. : -> Boolean



496
497
498
# File 'lib/rbi/type.rb', line 496

def boolean
  Boolean.new
end

.class_of(type, type_parameter = nil) ⇒ Object

Builds a type that represents the singleton class of another type like ‘T.class_of(Foo)`. : (Simple type, ?Type? type_parameter) -> ClassOf



534
535
536
# File 'lib/rbi/type.rb', line 534

def class_of(type, type_parameter = nil)
  ClassOf.new(type, type_parameter)
end

.generic(name, *params) ⇒ Object

Builds a type that represents a generic type like ‘T::Array` or `T::Hash[Symbol, Integer]`. : (String name, *(Type | Array) params) -> Generic



651
652
653
# File 'lib/rbi/type.rb', line 651

def generic(name, *params)
  T.unsafe(Generic).new(name, *params.flatten)
end

.nilable(type) ⇒ Object

Builds a type that represents a nilable of another type like ‘T.nilable(String)`.

Note that this method transforms types such as ‘T.nilable(T.untyped)` into `T.untyped`, so it may return something other than a `RBI::Type::Nilable`. : (Type type) -> Type



543
544
545
546
547
548
549
550
551
552
# File 'lib/rbi/type.rb', line 543

def nilable(type)
  # TODO: should we move this logic to a `flatten!`, `normalize!` or `simplify!` method?
  return type if type.is_a?(Untyped)

  if type.nilable?
    type
  else
    Nilable.new(type)
  end
end

.noreturnObject

Builds a type that represents ‘T.noreturn`. : -> NoReturn



502
503
504
# File 'lib/rbi/type.rb', line 502

def noreturn
  NoReturn.new
end

.parse_node(node) ⇒ Object

: (Prism::Node node) -> Type



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rbi/type_parser.rb', line 26

def parse_node(node)
  case node
  when Prism::ConstantReadNode, Prism::ConstantPathNode
    parse_constant(node)
  when Prism::CallNode
    parse_call(node)
  when Prism::ArrayNode
    parse_tuple(node)
  when Prism::HashNode, Prism::KeywordHashNode
    parse_shape(node)
  when Prism::ParenthesesNode
    body = node.body
    raise Error, "Expected exactly 1 child, got 0" unless body.is_a?(Prism::StatementsNode)

    children = body.body
    raise Error, "Expected exactly 1 child, got #{children.size}" unless children.size == 1

    parse_node(T.must(children.first))
  else
    raise Error, "Unexpected node `#{node}`"
  end
end

.parse_string(string) ⇒ Object

: (String string) -> Type

Raises:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/rbi/type_parser.rb', line 10

def parse_string(string)
  result = Prism.parse(string)
  unless result.success?
    raise Error, result.errors.map { |e| "#{e.message}." }.join(" ")
  end

  node = result.value
  raise Error, "Expected a type expression, got `#{node.class}`" unless node.is_a?(Prism::ProgramNode)
  raise Error, "Expected a type expression, got nothing" if node.statements.body.empty?
  raise Error, "Expected a single type expression, got `#{node.slice}`" if node.statements.body.size > 1

  node = T.must(node.statements.body.first)
  parse_node(node)
end

.procObject

Builds a type that represents a proc type like ‘T.proc.void`. : -> Proc



679
680
681
# File 'lib/rbi/type.rb', line 679

def proc
  Proc.new
end

.self_typeObject

Builds a type that represents ‘T.self_type`. : -> SelfType



508
509
510
# File 'lib/rbi/type.rb', line 508

def self_type
  SelfType.new
end

.shape(types = {}) ⇒ Object

Builds a type that represents a shape type like ‘String, age: Integer`. : (?Hash[(String | Symbol), Type] types) -> Shape



671
672
673
# File 'lib/rbi/type.rb', line 671

def shape(types = {})
  Shape.new(types)
end

.simple(name) ⇒ Object

Builds a simple type like ‘String` or `::Foo::Bar`.

It raises a ‘NameError` if the name is not a valid Ruby class identifier. : (String name) -> Simple

Raises:

  • (NameError)


473
474
475
476
477
478
# File 'lib/rbi/type.rb', line 473

def simple(name)
  # TODO: should we allow creating the instance anyway and move this to a `validate!` method?
  raise NameError, "Invalid type name: `#{name}`" unless valid_identifier?(name)

  Simple.new(name)
end

.t_class(type) ⇒ Object

Builds a type that represents the class of another type like ‘T::Class`. : (Type type) -> Class



528
529
530
# File 'lib/rbi/type.rb', line 528

def t_class(type)
  Class.new(type)
end

.tuple(*types) ⇒ Object

Builds a type that represents a tuple type like ‘[String, Integer]`. : (*(Type | Array) types) -> Tuple



665
666
667
# File 'lib/rbi/type.rb', line 665

def tuple(*types)
  Tuple.new(types.flatten)
end

.type_parameter(name) ⇒ Object

Builds a type that represents a type parameter like ‘T.type_parameter(:U)`. : (Symbol name) -> TypeParameter



657
658
659
# File 'lib/rbi/type.rb', line 657

def type_parameter(name)
  TypeParameter.new(name)
end

.untypedObject

Builds a type that represents ‘T.untyped`. : -> Untyped



514
515
516
# File 'lib/rbi/type.rb', line 514

def untyped
  Untyped.new
end

.voidObject

Builds a type that represents ‘void`. : -> Void



520
521
522
# File 'lib/rbi/type.rb', line 520

def void
  Void.new
end

Instance Method Details

#==(other) ⇒ Object



741
# File 'lib/rbi/type.rb', line 741

def ==(other); end

#eql?(other) ⇒ Boolean

: (BasicObject other) -> bool



744
745
746
# File 'lib/rbi/type.rb', line 744

def eql?(other)
  self == other
end

#hashObject

: -> Integer



750
751
752
# File 'lib/rbi/type.rb', line 750

def hash
  to_rbi.hash
end

#nilableObject

Returns a new type that is ‘nilable` if it is not already.

If the type is already nilable, it returns itself. “‘ruby type = RBI::Type.simple(“String”) type.to_rbi # => “String” type.nilable.to_rbi # => “T.nilable(String)” type.nilable.nilable.to_rbi # => “T.nilable(String)” “` : -> Type



709
710
711
# File 'lib/rbi/type.rb', line 709

def nilable
  Type.nilable(self)
end

#nilable?Boolean

Returns whether the type is nilable. : -> bool



736
737
738
# File 'lib/rbi/type.rb', line 736

def nilable?
  is_a?(Nilable)
end

#non_nilableObject

Returns the non-nilable version of the type. If the type is already non-nilable, it returns itself. If the type is nilable, it returns the inner type.

“‘ruby type = RBI::Type.nilable(RBI::Type.simple(“String”)) type.to_rbi # => “T.nilable(String)” type.non_nilable.to_rbi # => “String” type.non_nilable.non_nilable.to_rbi # => “String” “` : -> Type



724
725
726
727
728
729
730
731
732
# File 'lib/rbi/type.rb', line 724

def non_nilable
  # TODO: Should this logic be moved into a builder method?
  case self
  when Nilable
    type
  else
    self
  end
end

#rbs_stringObject

: -> String



1136
1137
1138
1139
1140
# File 'lib/rbi/rbs_printer.rb', line 1136

def rbs_string
  p = TypePrinter.new
  p.visit(self)
  p.string
end

#to_rbiObject



755
# File 'lib/rbi/type.rb', line 755

def to_rbi; end

#to_sObject

: -> String



759
760
761
# File 'lib/rbi/type.rb', line 759

def to_s
  to_rbi
end