parlour 3.0.0 → 5.0.0.beta.3

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  3. data/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  4. data/.gitignore +1 -1
  5. data/.parlour +5 -0
  6. data/.rspec +0 -0
  7. data/.travis.yml +0 -0
  8. data/CHANGELOG.md +57 -0
  9. data/CODE_OF_CONDUCT.md +0 -0
  10. data/Gemfile +0 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +233 -19
  13. data/Rakefile +0 -0
  14. data/exe/parlour +109 -4
  15. data/lib/parlour.rb +27 -1
  16. data/lib/parlour/conflict_resolver.rb +31 -9
  17. data/lib/parlour/conversion/converter.rb +34 -0
  18. data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
  19. data/lib/parlour/debugging.rb +0 -0
  20. data/lib/parlour/detached_rbi_generator.rb +1 -6
  21. data/lib/parlour/detached_rbs_generator.rb +25 -0
  22. data/lib/parlour/generator.rb +34 -0
  23. data/lib/parlour/kernel_hack.rb +0 -0
  24. data/lib/parlour/options.rb +71 -0
  25. data/lib/parlour/parse_error.rb +0 -0
  26. data/lib/parlour/plugin.rb +1 -1
  27. data/lib/parlour/rbi_generator.rb +24 -37
  28. data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
  29. data/lib/parlour/rbi_generator/attribute.rb +14 -5
  30. data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
  31. data/lib/parlour/rbi_generator/constant.rb +28 -8
  32. data/lib/parlour/rbi_generator/enum_class_namespace.rb +8 -3
  33. data/lib/parlour/rbi_generator/extend.rb +5 -2
  34. data/lib/parlour/rbi_generator/include.rb +5 -2
  35. data/lib/parlour/rbi_generator/method.rb +15 -10
  36. data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
  37. data/lib/parlour/rbi_generator/namespace.rb +68 -18
  38. data/lib/parlour/rbi_generator/parameter.rb +13 -7
  39. data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
  40. data/lib/parlour/rbi_generator/struct_class_namespace.rb +9 -2
  41. data/lib/parlour/rbi_generator/struct_prop.rb +12 -9
  42. data/lib/parlour/rbi_generator/type_alias.rb +101 -0
  43. data/lib/parlour/rbs_generator.rb +24 -0
  44. data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
  45. data/lib/parlour/rbs_generator/attribute.rb +82 -0
  46. data/lib/parlour/rbs_generator/block.rb +49 -0
  47. data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
  48. data/lib/parlour/rbs_generator/constant.rb +95 -0
  49. data/lib/parlour/rbs_generator/extend.rb +92 -0
  50. data/lib/parlour/rbs_generator/include.rb +92 -0
  51. data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
  52. data/lib/parlour/rbs_generator/method.rb +146 -0
  53. data/lib/parlour/rbs_generator/method_signature.rb +104 -0
  54. data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
  55. data/lib/parlour/rbs_generator/namespace.rb +627 -0
  56. data/lib/parlour/rbs_generator/parameter.rb +146 -0
  57. data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
  58. data/lib/parlour/rbs_generator/type_alias.rb +96 -0
  59. data/lib/parlour/type_loader.rb +25 -12
  60. data/lib/parlour/type_parser.rb +174 -17
  61. data/lib/parlour/typed_object.rb +87 -0
  62. data/lib/parlour/types.rb +539 -0
  63. data/lib/parlour/version.rb +1 -1
  64. data/parlour.gemspec +1 -1
  65. data/plugin_examples/foobar_plugin.rb +0 -0
  66. data/rbi/parlour.rbi +1856 -0
  67. metadata +35 -10
  68. data/lib/parlour/rbi_generator/options.rb +0 -74
@@ -0,0 +1,146 @@
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', 'extension', 'top', 'bot',
105
+ 'self', 'nil', 'void'
106
+ ]
107
+
108
+ # A mapping of {kind} values to the characteristic prefixes each kind has.
109
+ PREFIXES = {
110
+ normal: '',
111
+ splat: '*',
112
+ double_splat: '**',
113
+ }.freeze
114
+
115
+ sig { returns(String) }
116
+ # A string of how this parameter should be defined in an RBS signature.
117
+ #
118
+ # @return [String]
119
+ def to_rbs_param
120
+ raise 'blocks are not parameters in RBS' if kind == :block
121
+
122
+ t = String === @type ? @type : @type.generate_rbs
123
+ t = "^#{t}" if Types::Proc === @type
124
+
125
+ if RBS_KEYWORDS.include? name_without_kind
126
+ unless $VERBOSE.nil?
127
+ print Rainbow("Parlour warning: ").yellow.dark.bold
128
+ print Rainbow("RBS generation: ").magenta.bright.bold
129
+ puts "'#{name_without_kind}' is a keyword in RBS, renaming method parameter to '_#{name_without_kind}'"
130
+ end
131
+
132
+ n = "_#{name_without_kind}"
133
+ else
134
+ n = name_without_kind
135
+ end
136
+
137
+ # Extra check because "?*something" is invalid
138
+ ((required || (kind != :normal && kind != :keyword)) ? '' : '?') + if kind == :keyword
139
+ "#{n}: #{t}"
140
+ else
141
+ "#{PREFIXES[kind]}#{t} #{n}"
142
+ end
143
+ end
144
+ end
145
+ end
146
+ 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
@@ -10,27 +10,34 @@ module Parlour
10
10
  # TODO: make this into a class which stores configuration and passes it to
11
11
  # all typeparsers
12
12
 
13
- sig { params(source: String, filename: T.nilable(String)).returns(RbiGenerator::Namespace) }
13
+ sig { params(source: String, filename: T.nilable(String), generator: T.nilable(RbiGenerator)).returns(RbiGenerator::Namespace) }
14
14
  # Converts Ruby source code into a tree of objects.
15
15
  #
16
16
  # @param [String] source The Ruby source code.
17
17
  # @param [String, nil] filename The filename to use when parsing this code.
18
18
  # This may be used in error messages, but is optional.
19
19
  # @return [RbiGenerator::Namespace] The root of the object tree.
20
- def self.load_source(source, filename = nil)
21
- TypeParser.from_source(filename || '(source)', source).parse_all
20
+ def self.load_source(source, filename = nil, generator: nil)
21
+ TypeParser.from_source(filename || '(source)', source, generator: generator).parse_all
22
22
  end
23
23
 
24
- sig { params(filename: String).returns(RbiGenerator::Namespace) }
24
+ sig { params(filename: String, generator: T.nilable(RbiGenerator)).returns(RbiGenerator::Namespace) }
25
25
  # Converts Ruby source code into a tree of objects from a file.
26
26
  #
27
27
  # @param [String] filename The name of the file to load code from.
28
28
  # @return [RbiGenerator::Namespace] The root of the object tree.
29
- def self.load_file(filename)
30
- load_source(File.read(filename), filename)
29
+ def self.load_file(filename, generator: nil)
30
+ load_source(File.read(filename), filename, generator: generator)
31
+ end
32
+
33
+ sig do
34
+ params(
35
+ root: String,
36
+ inclusions: T::Array[String],
37
+ exclusions: T::Array[String],
38
+ generator: T.nilable(RbiGenerator),
39
+ ).returns(RbiGenerator::Namespace)
31
40
  end
32
-
33
- sig { params(root: String, exclusions: T::Array[String]).returns(RbiGenerator::Namespace) }
34
41
  # Loads an entire Sorbet project using Sorbet's file table, obeying any
35
42
  # "typed: ignore" sigils, into a tree of objects.
36
43
  #
@@ -39,12 +46,15 @@ module Parlour
39
46
  #
40
47
  # @param [String] root The root of the project; where the "sorbet" directory
41
48
  # and "Gemfile" are located.
49
+ # @param [Array<String>] inclusions A list of files to include when loading
50
+ # the project, relative to the given root.
42
51
  # @param [Array<String>] exclusions A list of files to exclude when loading
43
52
  # the project, relative to the given root.
44
53
  # @return [RbiGenerator::Namespace] The root of the object tree.
45
- def self.load_project(root, exclusions: [])
54
+ def self.load_project(root, inclusions: ['.'], exclusions: [], generator: nil)
55
+ expanded_inclusions = inclusions.map { |i| File.expand_path(i, root) }
46
56
  expanded_exclusions = exclusions.map { |e| File.expand_path(e, root) }
47
-
57
+
48
58
  stdin, stdout, stderr, wait_thr = T.unsafe(Open3).popen3(
49
59
  'bundle exec srb tc -p file-table-json',
50
60
  chdir: root
@@ -63,14 +73,17 @@ module Parlour
63
73
  path = File.expand_path(rel_path, root)
64
74
 
65
75
  # Skip this file if it was excluded
66
- next if expanded_exclusions.include?(path)
76
+ next if !expanded_inclusions.any? { |i| path.start_with?(i) } \
77
+ || expanded_exclusions.any? { |e| path.start_with?(e) }
67
78
 
68
79
  # There are some entries which are URLs to stdlib
69
80
  next unless File.exist?(path)
70
81
 
71
- namespaces << load_file(path)
82
+ namespaces << load_file(path, generator: generator)
72
83
  end
73
84
 
85
+ namespaces.uniq!
86
+
74
87
  raise 'project is empty' if namespaces.empty?
75
88
 
76
89
  first_namespace, *other_namespaces = namespaces
@@ -83,7 +83,7 @@ module Parlour
83
83
 
84
84
  extend T::Sig
85
85
 
86
- sig { params(ast: Parser::AST::Node, unknown_node_errors: T::Boolean).void }
86
+ sig { params(ast: Parser::AST::Node, unknown_node_errors: T::Boolean, generator: T.nilable(RbiGenerator)).void }
87
87
  # Creates a new {TypeParser} from whitequark/parser AST.
88
88
  #
89
89
  # @param [Parser::AST::Node] The AST.
@@ -92,25 +92,26 @@ module Parlour
92
92
  # if true, a parse error is raised. Setting this to true is likely to
93
93
  # raise errors for lots of non-RBI Ruby code, but setting it to false
94
94
  # could miss genuine typed objects if Parlour or your code contains a bug.
95
- def initialize(ast, unknown_node_errors: false)
95
+ def initialize(ast, unknown_node_errors: false, generator: nil)
96
96
  @ast = ast
97
97
  @unknown_node_errors = unknown_node_errors
98
+ @generator = generator || DetachedRbiGenerator.new
98
99
  end
99
100
 
100
- sig { params(filename: String, source: String).returns(TypeParser) }
101
+ sig { params(filename: String, source: String, generator: T.nilable(RbiGenerator)).returns(TypeParser) }
101
102
  # Creates a new {TypeParser} from a source file and its filename.
102
103
  #
103
104
  # @param [String] filename A filename. This does not need to be an actual
104
105
  # file; it merely identifies this source.
105
106
  # @param [String] source The Ruby source code.
106
107
  # @return [TypeParser]
107
- def self.from_source(filename, source)
108
+ def self.from_source(filename, source, generator: nil)
108
109
  buffer = Parser::Source::Buffer.new(filename)
109
110
  buffer.source = source
110
111
 
111
112
  # || special case handles parser returning nil on an empty file
112
113
  parsed = Parser::CurrentRuby.new.parse(buffer) || Parser::AST::Node.new(:body)
113
- TypeParser.new(parsed)
114
+ TypeParser.new(parsed, generator: generator)
114
115
  end
115
116
 
116
117
  sig { returns(Parser::AST::Node) }
@@ -122,12 +123,16 @@ module Parlour
122
123
  # is encountered.
123
124
  attr_reader :unknown_node_errors
124
125
 
126
+ sig { returns(RbiGenerator) }
127
+ # @return [RbiGenerator] The {RbiGenerator} to load the source into.
128
+ attr_accessor :generator
129
+
125
130
  # Parses the entire source file and returns the resulting root namespace.
126
131
  #
127
132
  # @return [RbiGenerator::Namespace] The root namespace of the parsed source.
128
133
  sig { returns(RbiGenerator::Namespace) }
129
134
  def parse_all
130
- root = RbiGenerator::Namespace.new(DetachedRbiGenerator.new)
135
+ root = generator.root
131
136
  root.children.concat(parse_path_to_object(NodePath.new([])))
132
137
  root
133
138
  end
@@ -162,7 +167,7 @@ module Parlour
162
167
  top_level = T.let(nil, T.nilable(RbiGenerator::Namespace))
163
168
  parent_names.each do |n|
164
169
  new_obj = RbiGenerator::Namespace.new(
165
- DetachedRbiGenerator.new,
170
+ generator,
166
171
  n.to_s,
167
172
  false,
168
173
  )
@@ -225,7 +230,7 @@ module Parlour
225
230
  end
226
231
 
227
232
  final_obj = RbiGenerator::StructClassNamespace.new(
228
- DetachedRbiGenerator.new,
233
+ generator,
229
234
  this_name.to_s,
230
235
  final,
231
236
  props,
@@ -248,7 +253,7 @@ module Parlour
248
253
  end
249
254
 
250
255
  final_obj = RbiGenerator::EnumClassNamespace.new(
251
- DetachedRbiGenerator.new,
256
+ generator,
252
257
  this_name.to_s,
253
258
  final,
254
259
  enums,
@@ -256,7 +261,7 @@ module Parlour
256
261
  )
257
262
  else
258
263
  final_obj = RbiGenerator::ClassNamespace.new(
259
- DetachedRbiGenerator.new,
264
+ generator,
260
265
  this_name.to_s,
261
266
  final,
262
267
  node_to_s(superclass),
@@ -288,7 +293,7 @@ module Parlour
288
293
  top_level = T.let(nil, T.nilable(RbiGenerator::Namespace))
289
294
  parent_names.each do |n|
290
295
  new_obj = RbiGenerator::Namespace.new(
291
- DetachedRbiGenerator.new,
296
+ generator,
292
297
  n.to_s,
293
298
  false,
294
299
  )
@@ -298,7 +303,7 @@ module Parlour
298
303
  end if parent_names
299
304
 
300
305
  final_obj = RbiGenerator::ModuleNamespace.new(
301
- DetachedRbiGenerator.new,
306
+ generator,
302
307
  this_name.to_s,
303
308
  final,
304
309
  interface,
@@ -341,7 +346,7 @@ module Parlour
341
346
  when :casgn
342
347
  _, name, body = *node
343
348
  [Parlour::RbiGenerator::Constant.new(
344
- DetachedRbiGenerator.new,
349
+ generator,
345
350
  name: T.must(name).to_s,
346
351
  value: T.must(node_to_s(body)),
347
352
  )]
@@ -535,7 +540,7 @@ module Parlour
535
540
  # There should only be one ever here, but future-proofing anyway
536
541
  def_names.map do |def_name|
537
542
  RbiGenerator::Method.new(
538
- DetachedRbiGenerator.new,
543
+ generator,
539
544
  def_name,
540
545
  parameters,
541
546
  return_type,
@@ -575,7 +580,7 @@ module Parlour
575
580
 
576
581
  def_names.map do |def_name|
577
582
  RbiGenerator::Attribute.new(
578
- DetachedRbiGenerator.new,
583
+ generator,
579
584
  def_name,
580
585
  attr_direction,
581
586
  attr_type,
@@ -662,7 +667,7 @@ module Parlour
662
667
  # There should only be one ever here, but future-proofing anyway
663
668
  def_names.map do |def_name|
664
669
  RbiGenerator::Method.new(
665
- DetachedRbiGenerator.new,
670
+ generator,
666
671
  def_name,
667
672
  parameters,
668
673
  return_type,
@@ -679,7 +684,7 @@ module Parlour
679
684
 
680
685
  def_names.map do |def_name|
681
686
  RbiGenerator::Attribute.new(
682
- DetachedRbiGenerator.new,
687
+ generator,
683
688
  def_name,
684
689
  attr_direction,
685
690
  attr_type,
@@ -691,8 +696,160 @@ module Parlour
691
696
  end
692
697
  end
693
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
+
694
840
  protected
695
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
+
696
853
  sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Array[Symbol]) }
697
854
  # Given a node representing a simple chain of constants (such as A or
698
855
  # A::B::C), converts that node into an array of the constant names which