parlour 4.0.0 → 5.0.0.beta.4

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 +0 -0
  6. data/.rspec +0 -0
  7. data/.travis.yml +0 -0
  8. data/CHANGELOG.md +52 -0
  9. data/CODE_OF_CONDUCT.md +0 -0
  10. data/Gemfile +0 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +208 -20
  13. data/Rakefile +0 -0
  14. data/exe/parlour +45 -6
  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 +229 -0
  19. data/lib/parlour/debugging.rb +0 -0
  20. data/lib/parlour/detached_rbi_generator.rb +0 -0
  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 +0 -0
  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 +17 -6
  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 +40 -13
  38. data/lib/parlour/rbi_generator/parameter.rb +11 -5
  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 +0 -0
  60. data/lib/parlour/type_parser.rb +174 -5
  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 +1404 -441
  67. metadata +33 -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
File without changes
@@ -345,11 +345,28 @@ module Parlour
345
345
  end.flatten
346
346
  when :casgn
347
347
  _, name, body = *node
348
- [Parlour::RbiGenerator::Constant.new(
349
- generator,
350
- name: T.must(name).to_s,
351
- value: T.must(node_to_s(body)),
352
- )]
348
+
349
+ # Determine whether this is a constant or a type alias
350
+ # A type alias looks like:
351
+ # (block (send (const nil :T) :type_alias) (args) (type_to_alias))
352
+ if body.type == :block &&
353
+ body.to_a[0].type == :send &&
354
+ body.to_a[0].to_a[0].type == :const &&
355
+ body.to_a[0].to_a[0].to_a == [nil, :T] &&
356
+ body.to_a[0].to_a[1] == :type_alias
357
+
358
+ [Parlour::RbiGenerator::TypeAlias.new(
359
+ generator,
360
+ name: T.must(name).to_s,
361
+ type: T.must(node_to_s(body.to_a[2])),
362
+ )]
363
+ else
364
+ [Parlour::RbiGenerator::Constant.new(
365
+ generator,
366
+ name: T.must(name).to_s,
367
+ value: T.must(node_to_s(body)),
368
+ )]
369
+ end
353
370
  else
354
371
  if unknown_node_errors
355
372
  parse_err "don't understand node type #{node.type}", node
@@ -696,8 +713,160 @@ module Parlour
696
713
  end
697
714
  end
698
715
 
716
+ sig { params(str: String).returns(Types::Type) }
717
+ # TODO doc
718
+ def self.parse_single_type(str)
719
+ i = TypeParser.from_source('(none)', str)
720
+ i.parse_node_to_type(i.ast)
721
+ end
722
+
723
+ sig { params(node: Parser::AST::Node).returns(Types::Type) }
724
+ # Given an AST node representing an RBI type (such as 'T::Array[String]'),
725
+ # parses it into a generic type.
726
+ #
727
+ # @param [Parser::AST::Node] node
728
+ # @return [Parlour::Types::Type]
729
+ def parse_node_to_type(node)
730
+ case node.type
731
+ when :send
732
+ target, message, *args = *node
733
+
734
+ # Special case: is this a generic type instantiation?
735
+ if message == :[]
736
+ names = constant_names(target)
737
+ known_single_element_collections = [:Array, :Set, :Range, :Enumerator, :Enumerable]
738
+
739
+ if names.length == 2 && names[0] == :T &&
740
+ known_single_element_collections.include?(names[1])
741
+
742
+ parse_err "no type in T::#{names[1]}[...]", node if args.nil? || args.empty?
743
+ parse_err "too many types in T::#{names[1]}[...]", node unless args.length == 1
744
+ return T.must(Types.const_get(T.must(names[1]))).new(parse_node_to_type(T.must(args.first)))
745
+ elsif names.length == 2 && names == [:T, :Hash]
746
+ parse_err "not enough types in T::Hash[...]", node if args.nil? || args.length < 2
747
+ parse_err "too many types in T::Hash[...]", node unless args.length == 2
748
+ return Types::Hash.new(
749
+ parse_node_to_type(args[0]), parse_node_to_type(args[1])
750
+ )
751
+ else
752
+ # TODO
753
+ warning "user-defined generic types not implemented, treating #{names.last} as untyped", node
754
+ return Types::Untyped.new
755
+ end
756
+ end
757
+
758
+ # Special case: is this a proc?
759
+ # This parsing is pretty simplified, but you'd also have to be doing
760
+ # something pretty cursed with procs to break this
761
+ # This checks for (send (send (send (const nil :T) :proc) ...) ...)
762
+ # That's the right amount of nesting for T.proc.params(...).returns(...)
763
+ if node.to_a[0].type == :send &&
764
+ node.to_a[0].to_a[0].type == :send &&
765
+ node.to_a[0].to_a[0].to_a[1] == :proc &&
766
+ node.to_a[0].to_a[0].to_a[0].type == :const &&
767
+ node.to_a[0].to_a[0].to_a[0].to_a == [nil, :T] # yuck
768
+
769
+ # Get parameters
770
+ params_send = node.to_a[0]
771
+ parse_err "expected 'params' to follow 'T.proc'", node unless params_send.to_a[1] == :params
772
+ parse_err "expected 'params' to have kwargs", node unless params_send.to_a[2].type == :hash
773
+
774
+ parameters = params_send.to_a[2].to_a.map do |pair|
775
+ name, value = *pair
776
+ parse_err "expected 'params' name to be symbol", node unless name.type == :sym
777
+ name = name.to_a[0].to_s
778
+ value = parse_node_to_type(value)
779
+
780
+ RbiGenerator::Parameter.new(name, type: value)
781
+ end
782
+
783
+ # Get return value
784
+ if node.to_a[1] == :void
785
+ return_type = nil
786
+ else
787
+ _, call, *args = *node
788
+ parse_err 'expected .returns or .void', node unless call == :returns
789
+ parse_err 'no argument to .returns', node if args.nil? || args.empty?
790
+ parse_err 'too many arguments to .returns', node unless args.length == 1
791
+ return_type = parse_node_to_type(T.must(args.first))
792
+ end
793
+
794
+ return Types::Proc.new(parameters, return_type)
795
+ end
796
+
797
+ # The other options for a valid call are all "T.something" methods
798
+ parse_err "unexpected call #{node_to_s(node).inspect} in type", node \
799
+ unless target.type == :const && target.to_a == [nil, :T]
800
+
801
+ case message
802
+ when :nilable
803
+ parse_err 'no argument to T.nilable', node if args.nil? || args.empty?
804
+ parse_err 'too many arguments to T.nilable', node unless args.length == 1
805
+ Types::Nilable.new(parse_node_to_type(T.must(args.first)))
806
+ when :any
807
+ Types::Union.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
808
+ when :all
809
+ Types::Intersection.new((args || []).map { |x| parse_node_to_type(T.must(x)) })
810
+ when :let
811
+ # Not really allowed in a type signature, but handy for generalizing
812
+ # constant types
813
+ parse_err 'not enough argument to T.let', node if args.nil? || args.length < 2
814
+ parse_err 'too many arguments to T.nilable', node unless args.length == 2
815
+ parse_node_to_type(args[1])
816
+ when :type_parameter
817
+ parse_err 'no argument to T.type_parameter', node if args.nil? || args.empty?
818
+ parse_err 'too many arguments to T.type_parameter', node unless args.length == 1
819
+ parse_err 'expected T.type_parameter to be passed a symbol', node unless T.must(args.first).type == :sym
820
+ Types::Raw.new(T.must(args.first.to_a[0].to_s))
821
+ when :class_of
822
+ parse_err 'no argument to T.class_of', node if args.nil? || args.empty?
823
+ parse_err 'too many arguments to T.class_of', node unless args.length == 1
824
+ Types::Class.new(parse_node_to_type(args[0]))
825
+ when :untyped
826
+ parse_err 'T.untyped does not accept arguments', node if !args.nil? && !args.empty?
827
+ Types::Untyped.new
828
+ else
829
+ warning "unknown method T.#{message}, treating as untyped", node
830
+ Types::Untyped.new
831
+ end
832
+ when :const
833
+ # Special case: T::Boolean
834
+ if constant_names(node) == [:T, :Boolean]
835
+ return Types::Boolean.new
836
+ end
837
+
838
+ # Otherwise, just a plain old constant
839
+ Types::Raw.new(constant_names(node).join('::'))
840
+ when :array
841
+ # Tuple
842
+ Types::Tuple.new(node.to_a.map { |x| parse_node_to_type(T.must(x)) })
843
+ when :hash
844
+ # Shape/record
845
+ keys_to_types = node.to_a.map do |pair|
846
+ key, value = *pair
847
+ parse_err "all shape keys must be symbols", node unless key.type == :sym
848
+ [key.to_a[0], parse_node_to_type(value)]
849
+ end.to_h
850
+
851
+ Types::Record.new(keys_to_types)
852
+ else
853
+ parse_err "unable to parse type #{node_to_s(node).inspect}", node
854
+ end
855
+ end
856
+
699
857
  protected
700
858
 
859
+ sig { params(msg: String, node: Parser::AST::Node).void }
860
+ def warning(msg, node)
861
+ return if $VERBOSE.nil?
862
+
863
+ print Rainbow("Parlour warning: ").yellow.dark.bold
864
+ print Rainbow("Type generalization: ").magenta.bright.bold
865
+ puts msg
866
+ print Rainbow(" └ at code: ").blue.bright.bold
867
+ puts node_to_s(node)
868
+ end
869
+
701
870
  sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Array[Symbol]) }
702
871
  # Given a node representing a simple chain of constants (such as A or
703
872
  # A::B::C), converts that node into an array of the constant names which