parlour 4.0.1 → 5.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +208 -20
  4. data/exe/parlour +45 -6
  5. data/lib/parlour.rb +27 -1
  6. data/lib/parlour/conversion/converter.rb +34 -0
  7. data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
  8. data/lib/parlour/detached_rbs_generator.rb +25 -0
  9. data/lib/parlour/generator.rb +34 -0
  10. data/lib/parlour/options.rb +71 -0
  11. data/lib/parlour/rbi_generator.rb +24 -37
  12. data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
  13. data/lib/parlour/rbi_generator/attribute.rb +14 -5
  14. data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
  15. data/lib/parlour/rbi_generator/constant.rb +17 -6
  16. data/lib/parlour/rbi_generator/enum_class_namespace.rb +8 -3
  17. data/lib/parlour/rbi_generator/extend.rb +5 -2
  18. data/lib/parlour/rbi_generator/include.rb +5 -2
  19. data/lib/parlour/rbi_generator/method.rb +15 -10
  20. data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
  21. data/lib/parlour/rbi_generator/namespace.rb +39 -12
  22. data/lib/parlour/rbi_generator/parameter.rb +11 -5
  23. data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
  24. data/lib/parlour/rbi_generator/struct_class_namespace.rb +9 -2
  25. data/lib/parlour/rbi_generator/struct_prop.rb +12 -9
  26. data/lib/parlour/rbi_generator/type_alias.rb +101 -0
  27. data/lib/parlour/rbs_generator.rb +24 -0
  28. data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
  29. data/lib/parlour/rbs_generator/attribute.rb +82 -0
  30. data/lib/parlour/rbs_generator/block.rb +49 -0
  31. data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
  32. data/lib/parlour/rbs_generator/constant.rb +95 -0
  33. data/lib/parlour/rbs_generator/extend.rb +92 -0
  34. data/lib/parlour/rbs_generator/include.rb +92 -0
  35. data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
  36. data/lib/parlour/rbs_generator/method.rb +146 -0
  37. data/lib/parlour/rbs_generator/method_signature.rb +104 -0
  38. data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
  39. data/lib/parlour/rbs_generator/namespace.rb +627 -0
  40. data/lib/parlour/rbs_generator/parameter.rb +145 -0
  41. data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
  42. data/lib/parlour/rbs_generator/type_alias.rb +96 -0
  43. data/lib/parlour/type_parser.rb +152 -0
  44. data/lib/parlour/typed_object.rb +87 -0
  45. data/lib/parlour/types.rb +445 -0
  46. data/lib/parlour/version.rb +1 -1
  47. data/parlour.gemspec +1 -1
  48. data/rbi/parlour.rbi +982 -76
  49. metadata +30 -7
  50. data/lib/parlour/rbi_generator/options.rb +0 -74
@@ -0,0 +1,145 @@
1
+ # typed: true
2
+ module Parlour
3
+ class RbsGenerator < Generator
4
+ # Represents a method parameter with a Sorbet type signature.
5
+ class Parameter
6
+ extend T::Sig
7
+
8
+ sig do
9
+ params(
10
+ name: String,
11
+ type: T.nilable(Types::TypeLike),
12
+ required: T::Boolean,
13
+ ).void
14
+ end
15
+ # Create a new method parameter.
16
+ # Note that, in RBS, blocks are not parameters. Use a {Block} instead.
17
+ #
18
+ # @example Create a simple Integer parameter named +num+.
19
+ # Parlour::RbsGenerator::Parameter.new('num', type: 'Integer')
20
+ # @example Create a nilable array parameter.
21
+ # Parlour::RbsGenerator::Parameter.new('array_of_strings_or_symbols', type:
22
+ # Parlour::Types::Nilable.new(
23
+ # Parlour::Types::Array.new(
24
+ # Parlour::Types::Union.new('String', 'Symbol')
25
+ # )
26
+ # )
27
+ # )
28
+ # @example Create an optional parameter.
29
+ # Parlour::RbsGenerator::Parameter.new('name', type: 'String', default: 'Parlour')
30
+ #
31
+ # @param name [String] The name of this parameter. This may start with +*+ or +**+,
32
+ # ,or end with +:+, which will infer the {kind} of this
33
+ # parameter. (If it contains none of those, {kind} will be +:normal+.)
34
+ # @param type [Types::TypeLike, nil] This type of this parameter.
35
+ # @param required [Boolean] Whether this parameter is required.
36
+ # @return [void]
37
+ def initialize(name, type: nil, required: true)
38
+ name = T.must(name)
39
+ @name = name
40
+
41
+ prefix = /^(\*\*|\*|\&)?/.match(name)&.captures&.first || ''
42
+ @kind = PREFIXES.rassoc(prefix).first
43
+
44
+ @kind = :keyword if kind == :normal && name.end_with?(':')
45
+
46
+ @type = type || Parlour::Types::Untyped.new
47
+ @required = required
48
+ end
49
+
50
+ sig { params(other: Object).returns(T::Boolean) }
51
+ # Returns true if this instance is equal to another method.
52
+ #
53
+ # @param other [Object] The other instance. If this is not a {Parameter} (or a
54
+ # subclass of it), this will always return false.
55
+ # @return [Boolean]
56
+ def ==(other)
57
+ Parameter === other &&
58
+ name == other.name &&
59
+ kind == other.kind &&
60
+ type == other.type &&
61
+ required == other.required
62
+ end
63
+
64
+ sig { returns(String) }
65
+ # The name of this parameter, including any prefixes or suffixes such as
66
+ # +*+.
67
+ # @return [String]
68
+ attr_reader :name
69
+
70
+ sig { returns(String) }
71
+ # The name of this parameter, stripped of any prefixes or suffixes. For
72
+ # example, +*rest+ would become +rest+, or +foo:+ would become +foo+.
73
+ #
74
+ # @return [String]
75
+ def name_without_kind
76
+ return T.must(name[0..-2]) if kind == :keyword
77
+
78
+ prefix_match = /^(\*\*|\*|\&)?[a-zA-Z_]/.match(name)
79
+ raise 'unknown prefix' unless prefix_match
80
+ prefix = prefix_match.captures.first || ''
81
+ T.must(name[prefix.length..-1])
82
+ end
83
+
84
+ sig { returns(Types::TypeLike) }
85
+ # This parameter's type.
86
+ # @return [String]
87
+ attr_reader :type
88
+
89
+ sig { returns(T::Boolean) }
90
+ # Whether this parameter is required.
91
+ # @return [Boolean]
92
+ attr_reader :required
93
+
94
+ sig { returns(Symbol) }
95
+ # The kind of parameter that this is. This will be one of +:normal+,
96
+ # +:splat+, +:double_splat+, or +:keyword+.
97
+ # @return [Symbol]
98
+ attr_reader :kind
99
+
100
+ # An array of reserved keywords in RBS which may be used as parameter
101
+ # names in standard Ruby.
102
+ # TODO: probably incomplete
103
+ RBS_KEYWORDS = [
104
+ 'type', 'interface', 'out', 'in', 'instance'
105
+ ]
106
+
107
+ # A mapping of {kind} values to the characteristic prefixes each kind has.
108
+ PREFIXES = {
109
+ normal: '',
110
+ splat: '*',
111
+ double_splat: '**',
112
+ }.freeze
113
+
114
+ sig { returns(String) }
115
+ # A string of how this parameter should be defined in an RBS signature.
116
+ #
117
+ # @return [String]
118
+ def to_rbs_param
119
+ raise 'blocks are not parameters in RBS' if kind == :block
120
+
121
+ t = String === @type ? @type : @type.generate_rbs
122
+ t = "^#{t}" if Types::Proc === @type
123
+
124
+ if RBS_KEYWORDS.include? name_without_kind
125
+ unless $VERBOSE.nil?
126
+ print Rainbow("Parlour warning: ").yellow.dark.bold
127
+ print Rainbow("Type generalization: ").magenta.bright.bold
128
+ puts "'#{name_without_kind}' is a keyword in RBS, renaming method parameter to '_#{name_without_kind}'"
129
+ end
130
+
131
+ n = "_#{name_without_kind}"
132
+ else
133
+ n = name_without_kind
134
+ end
135
+
136
+ # Extra check because "?*something" is invalid
137
+ ((required || (kind != :normal && kind != :keyword)) ? '' : '?') + if kind == :keyword
138
+ "#{n}: #{t}"
139
+ else
140
+ "#{PREFIXES[kind]}#{t} #{n}"
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,78 @@
1
+ # typed: true
2
+ module Parlour
3
+ class RbsGenerator < Generator
4
+ # An abstract class which is subclassed by any classes which can generate
5
+ # entire lines of an RBS, such as {Namespace} and {Method}. (As an example,
6
+ # {Parameter} is _not_ a subclass because it does not generate lines, only
7
+ # segments of definition lines.)
8
+ # @abstract
9
+ class RbsObject < TypedObject
10
+ abstract!
11
+
12
+ sig { params(generator: Generator, name: String).void }
13
+ # Creates a new RBS object.
14
+ # @note Don't call this directly.
15
+ #
16
+ # @param generator [RbsGenerator] The current RbsGenerator.
17
+ # @param name [String] The name of this module.
18
+ # @return [void]
19
+ def initialize(generator, name)
20
+ super(name)
21
+ @generator = generator
22
+ @generated_by = RbsGenerator === generator ? generator.current_plugin : nil
23
+ end
24
+
25
+ sig { returns(Generator) }
26
+ # The generator which this object belongs to.
27
+ # @return [Generator]
28
+ attr_reader :generator
29
+
30
+ sig do
31
+ abstract.params(
32
+ indent_level: Integer,
33
+ options: Options
34
+ ).returns(T::Array[String])
35
+ end
36
+ # Generates the RBS lines for this object.
37
+ #
38
+ # @abstract
39
+ # @param indent_level [Integer] The indentation level to generate the lines at.
40
+ # @param options [Options] The formatting options to use.
41
+ # @return [Array<String>] The RBS lines, formatted as specified.
42
+ def generate_rbs(indent_level, options); end
43
+
44
+ sig do
45
+ abstract.params(
46
+ others: T::Array[RbsGenerator::RbsObject]
47
+ ).returns(T::Boolean)
48
+ end
49
+ # Given an array of other objects, returns true if they may be merged
50
+ # into this instance using {merge_into_self}. Each subclass will have its
51
+ # own criteria on what allows objects to be mergeable.
52
+ #
53
+ # @abstract
54
+ # @param others [Array<RbsGenerator::RbsObject>] An array of other {RbsObject} instances.
55
+ # @return [Boolean] Whether this instance may be merged with them.
56
+ def mergeable?(others); end
57
+
58
+ sig do
59
+ abstract.params(
60
+ others: T::Array[RbsGenerator::RbsObject]
61
+ ).void
62
+ end
63
+ # Given an array of other objects, merges them into this one. Each
64
+ # subclass will do this differently.
65
+ # You MUST ensure that {mergeable?} is true for those instances.
66
+ #
67
+ # @abstract
68
+ # @param others [Array<RbsGenerator::RbsObject>] An array of other {RbsObject} instances.
69
+ # @return [void]
70
+ def merge_into_self(others); end
71
+
72
+ sig { overridable.override.returns(String) }
73
+ def describe
74
+ 'RBS object'
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,96 @@
1
+ # typed: true
2
+ module Parlour
3
+ class RbsGenerator < Generator
4
+ # Represents a type alias.
5
+ class TypeAlias < RbsObject
6
+ sig do
7
+ params(
8
+ generator: Generator,
9
+ name: String,
10
+ type: Types::TypeLike,
11
+ block: T.nilable(T.proc.params(x: TypeAlias).void)
12
+ ).void
13
+ end
14
+ # Creates a new type alias.
15
+ #
16
+ # @param name [String] The name of the alias.
17
+ # @param value [String] The type to alias to.
18
+ def initialize(generator, name:, type:, &block)
19
+ super(generator, name)
20
+ @type = type
21
+ yield_self(&block) if block
22
+ end
23
+
24
+ # @return [String] The type to alias to.
25
+ sig { returns(Types::TypeLike) }
26
+ attr_reader :type
27
+
28
+ sig { params(other: Object).returns(T::Boolean) }
29
+ # Returns true if this instance is equal to another type alias.
30
+ #
31
+ # @param other [Object] The other instance. If this is not a {TypeAlias} (or a
32
+ # subclass of it), this will always return false.
33
+ # @return [Boolean]
34
+ def ==(other)
35
+ TypeAlias === other && name == other.name && type == other.type
36
+ end
37
+
38
+ sig do
39
+ override.params(
40
+ indent_level: Integer,
41
+ options: Options
42
+ ).returns(T::Array[String])
43
+ end
44
+ # Generates the RBS lines for this type alias.
45
+ #
46
+ # @param indent_level [Integer] The indentation level to generate the lines at.
47
+ # @param options [Options] The formatting options to use.
48
+ # @return [Array<String>] The RBS lines, formatted as specified.
49
+ def generate_rbs(indent_level, options)
50
+ [options.indented(indent_level,
51
+ "type #{name} = #{String === @type ? @type : @type.generate_rbs}"
52
+ )]
53
+ end
54
+
55
+ sig do
56
+ override.params(
57
+ others: T::Array[RbsGenerator::RbsObject]
58
+ ).returns(T::Boolean)
59
+ end
60
+ # Given an array of {TypeAlias} instances, returns true if they may be
61
+ # merged into this instance using {merge_into_self}. This is always false.
62
+ #
63
+ # @param others [Array<RbiGenerator::RbsObject>] An array of other
64
+ # {TypeAlias} instances.
65
+ # @return [Boolean] Whether this instance may be merged with them.
66
+ def mergeable?(others)
67
+ others.all? { |other| self == other }
68
+ end
69
+
70
+ sig do
71
+ override.params(
72
+ others: T::Array[RbsGenerator::RbsObject]
73
+ ).void
74
+ end
75
+ # Given an array of {TypeAlias} instances, merges them into this one.
76
+ # This particular implementation will simply do nothing, as instances
77
+ # are only mergeable if they are indentical.
78
+ # You MUST ensure that {mergeable?} is true for those instances.
79
+ #
80
+ # @param others [Array<RbiGenerator::RbsObject>] An array of other
81
+ # {TypeAlias} instances.
82
+ # @return [void]
83
+ def merge_into_self(others)
84
+ # We don't need to change anything! We only merge identical type alias
85
+ end
86
+
87
+ sig { override.returns(String) }
88
+ # Returns a human-readable brief string description of this code.
89
+ #
90
+ # @return [String]
91
+ def describe
92
+ "Type Alias (#{name} = #{type})"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -696,8 +696,160 @@ module Parlour
696
696
  end
697
697
  end
698
698
 
699
+ sig { params(str: String).returns(Types::Type) }
700
+ # TODO doc
701
+ def self.parse_single_type(str)
702
+ i = TypeParser.from_source('(none)', str)
703
+ i.parse_node_to_type(i.ast)
704
+ end
705
+
706
+ sig { params(node: Parser::AST::Node).returns(Types::Type) }
707
+ # Given an AST node representing an RBI type (such as 'T::Array[String]'),
708
+ # parses it into a generic type.
709
+ #
710
+ # @param [Parser::AST::Node] node
711
+ # @return [Parlour::Types::Type]
712
+ def parse_node_to_type(node)
713
+ case node.type
714
+ when :send
715
+ target, message, *args = *node
716
+
717
+ # Special case: is this a generic type instantiation?
718
+ if message == :[]
719
+ names = constant_names(target)
720
+ known_single_element_collections = [:Array, :Set, :Range, :Enumerator, :Enumerable]
721
+
722
+ if names.length == 2 && names[0] == :T &&
723
+ known_single_element_collections.include?(names[1])
724
+
725
+ parse_err "no type in T::#{names[1]}[...]", node if args.nil? || args.empty?
726
+ parse_err "too many types in T::#{names[1]}[...]", node unless args.length == 1
727
+ return T.must(Types.const_get(T.must(names[1]))).new(parse_node_to_type(T.must(args.first)))
728
+ elsif names.length == 2 && names == [:T, :Hash]
729
+ parse_err "not enough types in T::Hash[...]", node if args.nil? || args.length < 2
730
+ parse_err "too many types in T::Hash[...]", node unless args.length == 2
731
+ return Types::Hash.new(
732
+ parse_node_to_type(args[0]), parse_node_to_type(args[1])
733
+ )
734
+ else
735
+ # TODO
736
+ warning "user-defined generic types not implemented, treating #{names.last} as untyped", node
737
+ return Types::Untyped.new
738
+ end
739
+ end
740
+
741
+ # Special case: is this a proc?
742
+ # This parsing is pretty simplified, but you'd also have to be doing
743
+ # something pretty cursed with procs to break this
744
+ # This checks for (send (send (send (const nil :T) :proc) ...) ...)
745
+ # That's the right amount of nesting for T.proc.params(...).returns(...)
746
+ if node.to_a[0].type == :send &&
747
+ node.to_a[0].to_a[0].type == :send &&
748
+ node.to_a[0].to_a[0].to_a[1] == :proc &&
749
+ node.to_a[0].to_a[0].to_a[0].type == :const &&
750
+ node.to_a[0].to_a[0].to_a[0].to_a == [nil, :T] # yuck
751
+
752
+ # Get parameters
753
+ params_send = node.to_a[0]
754
+ parse_err "expected 'params' to follow 'T.proc'", node unless params_send.to_a[1] == :params
755
+ parse_err "expected 'params' to have kwargs", node unless params_send.to_a[2].type == :hash
756
+
757
+ parameters = params_send.to_a[2].to_a.map do |pair|
758
+ name, value = *pair
759
+ parse_err "expected 'params' name to be symbol", node unless name.type == :sym
760
+ name = name.to_a[0].to_s
761
+ value = parse_node_to_type(value)
762
+
763
+ RbiGenerator::Parameter.new(name, type: value)
764
+ end
765
+
766
+ # Get return value
767
+ if node.to_a[1] == :void
768
+ return_type = nil
769
+ else
770
+ _, call, *args = *node
771
+ parse_err 'expected .returns or .void', node unless call == :returns
772
+ parse_err 'no argument to .returns', node if args.nil? || args.empty?
773
+ parse_err 'too many arguments to .returns', node unless args.length == 1
774
+ return_type = parse_node_to_type(T.must(args.first))
775
+ end
776
+
777
+ return Types::Proc.new(parameters, return_type)
778
+ end
779
+
780
+ # The other options for a valid call are all "T.something" methods
781
+ parse_err "unexpected call #{node_to_s(node).inspect} in type", node \
782
+ unless target.type == :const && target.to_a == [nil, :T]
783
+
784
+ case message
785
+ when :nilable
786
+ parse_err 'no argument to T.nilable', node if args.nil? || args.empty?
787
+ parse_err 'too many arguments to T.nilable', node unless args.length == 1
788
+ Types::Nilable.new(parse_node_to_type(T.must(args.first)))
789
+ when :any
790
+ Types::Union.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
791
+ when :all
792
+ Types::Intersection.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
793
+ when :let
794
+ # Not really allowed in a type signature, but handy for generalizing
795
+ # constant types
796
+ parse_err 'not enough argument to T.let', node if args.nil? || args.length < 2
797
+ parse_err 'too many arguments to T.nilable', node unless args.length == 2
798
+ parse_node_to_type(args[1])
799
+ when :type_parameter
800
+ parse_err 'no argument to T.type_parameter', node if args.nil? || args.empty?
801
+ parse_err 'too many arguments to T.type_parameter', node unless args.length == 1
802
+ parse_err 'expected T.type_parameter to be passed a symbol', node unless T.must(args.first).type == :sym
803
+ Types::Raw.new(T.must(args.first.to_a[0].to_s))
804
+ when :class_of
805
+ parse_err 'no argument to T.class_of', node if args.nil? || args.empty?
806
+ parse_err 'too many arguments to T.class_of', node unless args.length == 1
807
+ Types::Class.new(parse_node_to_type(args[0]))
808
+ when :untyped
809
+ parse_err 'T.untyped does not accept arguments', node if !args.nil? && !args.empty?
810
+ Types::Untyped.new
811
+ else
812
+ warning "unknown method T.#{message}, treating as untyped", node
813
+ Types::Untyped.new
814
+ end
815
+ when :const
816
+ # Special case: T::Boolean
817
+ if constant_names(node) == [:T, :Boolean]
818
+ return Types::Boolean.new
819
+ end
820
+
821
+ # Otherwise, just a plain old constant
822
+ Types::Raw.new(constant_names(node).join('::'))
823
+ when :array
824
+ # Tuple
825
+ Types::Tuple.new(node.to_a.map { |x| parse_node_to_type(T.must(x)) })
826
+ when :hash
827
+ # Shape/record
828
+ keys_to_types = node.to_a.map do |pair|
829
+ key, value = *pair
830
+ parse_err "all shape keys must be symbols", node unless key.type == :sym
831
+ [key.to_a[0], parse_node_to_type(value)]
832
+ end.to_h
833
+
834
+ Types::Record.new(keys_to_types)
835
+ else
836
+ parse_err "unable to parse type #{node_to_s(node).inspect}", node
837
+ end
838
+ end
839
+
699
840
  protected
700
841
 
842
+ sig { params(msg: String, node: Parser::AST::Node).void }
843
+ def warning(msg, node)
844
+ return if $VERBOSE.nil?
845
+
846
+ print Rainbow("Parlour warning: ").yellow.dark.bold
847
+ print Rainbow("Type generalization: ").magenta.bright.bold
848
+ puts msg
849
+ print Rainbow(" └ at code: ").blue.bright.bold
850
+ puts node_to_s(node)
851
+ end
852
+
701
853
  sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Array[Symbol]) }
702
854
  # Given a node representing a simple chain of constants (such as A or
703
855
  # A::B::C), converts that node into an array of the constant names which