parlour 4.0.1 → 5.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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