parlour 4.0.0 → 5.0.0.beta.4

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