parlour 2.0.0 → 5.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- data/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- data/.gitignore +1 -1
- data/.parlour +5 -0
- data/.rspec +0 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +64 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +233 -19
- data/Rakefile +0 -0
- data/exe/parlour +109 -4
- data/lib/parlour.rb +29 -1
- data/lib/parlour/conflict_resolver.rb +75 -27
- data/lib/parlour/conversion/converter.rb +34 -0
- data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
- data/lib/parlour/debugging.rb +0 -0
- data/lib/parlour/detached_rbi_generator.rb +1 -6
- data/lib/parlour/detached_rbs_generator.rb +25 -0
- data/lib/parlour/generator.rb +34 -0
- data/lib/parlour/kernel_hack.rb +0 -0
- data/lib/parlour/options.rb +71 -0
- data/lib/parlour/parse_error.rb +0 -0
- data/lib/parlour/plugin.rb +1 -1
- data/lib/parlour/rbi_generator.rb +24 -37
- data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
- data/lib/parlour/rbi_generator/attribute.rb +14 -5
- data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
- data/lib/parlour/rbi_generator/constant.rb +28 -8
- data/lib/parlour/rbi_generator/enum_class_namespace.rb +32 -5
- data/lib/parlour/rbi_generator/extend.rb +5 -2
- data/lib/parlour/rbi_generator/include.rb +5 -2
- data/lib/parlour/rbi_generator/method.rb +15 -10
- data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
- data/lib/parlour/rbi_generator/namespace.rb +115 -27
- data/lib/parlour/rbi_generator/parameter.rb +13 -7
- data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
- data/lib/parlour/rbi_generator/struct_class_namespace.rb +110 -0
- data/lib/parlour/rbi_generator/struct_prop.rb +139 -0
- data/lib/parlour/rbi_generator/type_alias.rb +101 -0
- data/lib/parlour/rbs_generator.rb +24 -0
- data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
- data/lib/parlour/rbs_generator/attribute.rb +82 -0
- data/lib/parlour/rbs_generator/block.rb +49 -0
- data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
- data/lib/parlour/rbs_generator/constant.rb +95 -0
- data/lib/parlour/rbs_generator/extend.rb +92 -0
- data/lib/parlour/rbs_generator/include.rb +92 -0
- data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
- data/lib/parlour/rbs_generator/method.rb +146 -0
- data/lib/parlour/rbs_generator/method_signature.rb +104 -0
- data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
- data/lib/parlour/rbs_generator/namespace.rb +627 -0
- data/lib/parlour/rbs_generator/parameter.rb +145 -0
- data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
- data/lib/parlour/rbs_generator/type_alias.rb +96 -0
- data/lib/parlour/type_loader.rb +30 -10
- data/lib/parlour/type_parser.rb +440 -43
- data/lib/parlour/typed_object.rb +87 -0
- data/lib/parlour/types.rb +445 -0
- data/lib/parlour/version.rb +1 -1
- data/parlour.gemspec +2 -2
- data/plugin_examples/foobar_plugin.rb +0 -0
- data/rbi/parlour.rbi +1799 -0
- metadata +42 -15
- 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
|
data/lib/parlour/type_loader.rb
CHANGED
@@ -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).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,8 +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.
|
51
|
+
# @param [Array<String>] exclusions A list of files to exclude when loading
|
52
|
+
# the project, relative to the given root.
|
42
53
|
# @return [RbiGenerator::Namespace] The root of the object tree.
|
43
|
-
def self.load_project(root)
|
54
|
+
def self.load_project(root, inclusions: ['.'], exclusions: [], generator: nil)
|
55
|
+
expanded_inclusions = inclusions.map { |i| File.expand_path(i, root) }
|
56
|
+
expanded_exclusions = exclusions.map { |e| File.expand_path(e, root) }
|
57
|
+
|
44
58
|
stdin, stdout, stderr, wait_thr = T.unsafe(Open3).popen3(
|
45
59
|
'bundle exec srb tc -p file-table-json',
|
46
60
|
chdir: root
|
@@ -58,12 +72,18 @@ module Parlour
|
|
58
72
|
next if rel_path.start_with?('./sorbet/rbi/hidden-definitions/')
|
59
73
|
path = File.expand_path(rel_path, root)
|
60
74
|
|
75
|
+
# Skip this file if it was excluded
|
76
|
+
next if !expanded_inclusions.any? { |i| path.start_with?(i) } \
|
77
|
+
|| expanded_exclusions.any? { |e| path.start_with?(e) }
|
78
|
+
|
61
79
|
# There are some entries which are URLs to stdlib
|
62
80
|
next unless File.exist?(path)
|
63
81
|
|
64
|
-
namespaces << load_file(path)
|
82
|
+
namespaces << load_file(path, generator: generator)
|
65
83
|
end
|
66
84
|
|
85
|
+
namespaces.uniq!
|
86
|
+
|
67
87
|
raise 'project is empty' if namespaces.empty?
|
68
88
|
|
69
89
|
first_namespace, *other_namespaces = namespaces
|
data/lib/parlour/type_parser.rb
CHANGED
@@ -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,23 +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
|
113
|
+
parsed = Parser::CurrentRuby.new.parse(buffer) || Parser::AST::Node.new(:body)
|
114
|
+
TypeParser.new(parsed, generator: generator)
|
112
115
|
end
|
113
116
|
|
114
117
|
sig { returns(Parser::AST::Node) }
|
@@ -117,15 +120,19 @@ module Parlour
|
|
117
120
|
|
118
121
|
sig { returns(T::Boolean) }
|
119
122
|
# @return [Boolean] Whether to raise an error if a node of an unknown kind
|
120
|
-
# is encountered.
|
123
|
+
# is encountered.
|
121
124
|
attr_reader :unknown_node_errors
|
122
125
|
|
126
|
+
sig { returns(RbiGenerator) }
|
127
|
+
# @return [RbiGenerator] The {RbiGenerator} to load the source into.
|
128
|
+
attr_accessor :generator
|
129
|
+
|
123
130
|
# Parses the entire source file and returns the resulting root namespace.
|
124
131
|
#
|
125
132
|
# @return [RbiGenerator::Namespace] The root namespace of the parsed source.
|
126
133
|
sig { returns(RbiGenerator::Namespace) }
|
127
134
|
def parse_all
|
128
|
-
root =
|
135
|
+
root = generator.root
|
129
136
|
root.children.concat(parse_path_to_object(NodePath.new([])))
|
130
137
|
root
|
131
138
|
end
|
@@ -134,7 +141,7 @@ module Parlour
|
|
134
141
|
# represents and returns it, recursing to any child namespaces and parsing
|
135
142
|
# any methods within.
|
136
143
|
#
|
137
|
-
# If the node directly represents several nodes, such as being a
|
144
|
+
# If the node directly represents several nodes, such as being a
|
138
145
|
# (begin ...) node, they are all returned.
|
139
146
|
#
|
140
147
|
# @param [NodePath] path The path to the namespace definition. Do not pass
|
@@ -144,7 +151,7 @@ module Parlour
|
|
144
151
|
sig { params(path: NodePath, is_within_eigenclass: T::Boolean).returns(T::Array[RbiGenerator::RbiObject]) }
|
145
152
|
def parse_path_to_object(path, is_within_eigenclass: false)
|
146
153
|
node = path.traverse(ast)
|
147
|
-
|
154
|
+
|
148
155
|
case node.type
|
149
156
|
when :class
|
150
157
|
parse_err 'cannot declare classes in an eigenclass', node if is_within_eigenclass
|
@@ -158,9 +165,9 @@ module Parlour
|
|
158
165
|
*parent_names, this_name = constant_names(name)
|
159
166
|
target = T.let(nil, T.nilable(RbiGenerator::Namespace))
|
160
167
|
top_level = T.let(nil, T.nilable(RbiGenerator::Namespace))
|
161
|
-
parent_names.each do |n|
|
168
|
+
parent_names.each do |n|
|
162
169
|
new_obj = RbiGenerator::Namespace.new(
|
163
|
-
|
170
|
+
generator,
|
164
171
|
n.to_s,
|
165
172
|
false,
|
166
173
|
)
|
@@ -169,18 +176,103 @@ module Parlour
|
|
169
176
|
top_level ||= new_obj
|
170
177
|
end if parent_names
|
171
178
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
179
|
+
# Instantiate the correct kind of class
|
180
|
+
if ['T::Struct', '::T::Struct'].include?(node_to_s(superclass))
|
181
|
+
# Find all of this struct's props and consts
|
182
|
+
# The body is typically a `begin` element but when there's only
|
183
|
+
# one node there's no wrapping block and instead it would directly
|
184
|
+
# be the node.
|
185
|
+
prop_nodes = body.nil? ? [] :
|
186
|
+
(body.type == :begin ? body.to_a : [body]).select { |x| x.type == :send && [:prop, :const].include?(x.to_a[1]) }
|
187
|
+
|
188
|
+
props = prop_nodes.map do |prop_node|
|
189
|
+
_, prop_type, name_node, type_node, extras_hash_node = *prop_node
|
190
|
+
|
191
|
+
# "const" is just "prop ..., immutable: true"
|
192
|
+
extras_hash = extras_hash_node.to_a.map do |pair_node|
|
193
|
+
key_node, value_node = *pair_node
|
194
|
+
parse_err 'prop/const key must be a symbol', prop_node unless key_node.type == :sym
|
195
|
+
key = key_node.to_a.first
|
196
|
+
|
197
|
+
value =
|
198
|
+
if key == :default
|
199
|
+
T.must(node_to_s(value_node))
|
200
|
+
else
|
201
|
+
case value_node.type
|
202
|
+
when :true
|
203
|
+
true
|
204
|
+
when :false
|
205
|
+
false
|
206
|
+
when :sym
|
207
|
+
value_node.to_a.first
|
208
|
+
else
|
209
|
+
T.must(node_to_s(value_node))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
[key, value]
|
214
|
+
end.to_h
|
215
|
+
|
216
|
+
if prop_type == :const
|
217
|
+
parse_err 'const cannot use immutable key', prop_node unless extras_hash[:immutable].nil?
|
218
|
+
extras_hash[:immutable] = true
|
219
|
+
end
|
220
|
+
|
221
|
+
# Get prop/const name
|
222
|
+
parse_err 'prop/const name must be a symbol or string', prop_node unless [:sym, :str].include?(name_node.type)
|
223
|
+
name = name_node.to_a.first.to_s
|
224
|
+
|
225
|
+
RbiGenerator::StructProp.new(
|
226
|
+
name,
|
227
|
+
T.must(node_to_s(type_node)),
|
228
|
+
**T.unsafe(extras_hash)
|
229
|
+
)
|
230
|
+
end
|
231
|
+
|
232
|
+
final_obj = RbiGenerator::StructClassNamespace.new(
|
233
|
+
generator,
|
234
|
+
this_name.to_s,
|
235
|
+
final,
|
236
|
+
props,
|
237
|
+
abstract,
|
238
|
+
)
|
239
|
+
elsif ['T::Enum', '::T::Enum'].include?(node_to_s(superclass))
|
240
|
+
# Look for (block (send nil :enums) ...) structure
|
241
|
+
enums_node = body.nil? ? nil :
|
242
|
+
(body.type == :begin ? body.to_a : [body]).find { |x| x.type == :block && x.to_a[0].type == :send && x.to_a[0].to_a[1] == :enums }
|
243
|
+
|
244
|
+
# Find the constant assigments within this block
|
245
|
+
constant_nodes = enums_node.to_a[2].to_a
|
246
|
+
|
247
|
+
# Convert this to an array to enums as EnumClassNamespace expects
|
248
|
+
enums = constant_nodes.map do |constant_node|
|
249
|
+
_, name, new_node = *constant_node
|
250
|
+
serialize_value = node_to_s(new_node.to_a[2])
|
251
|
+
|
252
|
+
serialize_value ? [name.to_s, serialize_value] : name.to_s
|
253
|
+
end
|
254
|
+
|
255
|
+
final_obj = RbiGenerator::EnumClassNamespace.new(
|
256
|
+
generator,
|
257
|
+
this_name.to_s,
|
258
|
+
final,
|
259
|
+
enums,
|
260
|
+
abstract,
|
261
|
+
)
|
262
|
+
else
|
263
|
+
final_obj = RbiGenerator::ClassNamespace.new(
|
264
|
+
generator,
|
265
|
+
this_name.to_s,
|
266
|
+
final,
|
267
|
+
node_to_s(superclass),
|
268
|
+
abstract,
|
269
|
+
)
|
182
270
|
end
|
183
271
|
|
272
|
+
final_obj.children.concat(parse_path_to_object(path.child(2))) if body
|
273
|
+
final_obj.create_includes(includes)
|
274
|
+
final_obj.create_extends(extends)
|
275
|
+
|
184
276
|
if target
|
185
277
|
target.children << final_obj
|
186
278
|
[top_level]
|
@@ -199,9 +291,9 @@ module Parlour
|
|
199
291
|
*parent_names, this_name = constant_names(name)
|
200
292
|
target = T.let(nil, T.nilable(RbiGenerator::Namespace))
|
201
293
|
top_level = T.let(nil, T.nilable(RbiGenerator::Namespace))
|
202
|
-
parent_names.each do |n|
|
294
|
+
parent_names.each do |n|
|
203
295
|
new_obj = RbiGenerator::Namespace.new(
|
204
|
-
|
296
|
+
generator,
|
205
297
|
n.to_s,
|
206
298
|
false,
|
207
299
|
)
|
@@ -211,7 +303,7 @@ module Parlour
|
|
211
303
|
end if parent_names
|
212
304
|
|
213
305
|
final_obj = RbiGenerator::ModuleNamespace.new(
|
214
|
-
|
306
|
+
generator,
|
215
307
|
this_name.to_s,
|
216
308
|
final,
|
217
309
|
interface,
|
@@ -230,24 +322,34 @@ module Parlour
|
|
230
322
|
when :send, :block
|
231
323
|
if sig_node?(node)
|
232
324
|
parse_sig_into_methods(path, is_within_eigenclass: is_within_eigenclass)
|
325
|
+
elsif node.type == :send &&
|
326
|
+
[:attr_reader, :attr_writer, :attr_accessor].include?(node.to_a[1]) &&
|
327
|
+
!previous_sibling_sig_node?(path)
|
328
|
+
parse_method_into_methods(path, is_within_eigenclass: is_within_eigenclass)
|
233
329
|
else
|
234
330
|
[]
|
235
331
|
end
|
236
332
|
when :def, :defs
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
[]
|
333
|
+
if previous_sibling_sig_node?(path)
|
334
|
+
[]
|
335
|
+
else
|
336
|
+
parse_method_into_methods(path, is_within_eigenclass: is_within_eigenclass)
|
337
|
+
end
|
243
338
|
when :sclass
|
244
339
|
parse_err 'cannot access eigen of non-self object', node unless node.to_a[0].type == :self
|
245
340
|
parse_path_to_object(path.child(1), is_within_eigenclass: true)
|
246
341
|
when :begin
|
247
342
|
# Just map over all the things
|
248
343
|
node.to_a.length.times.map do |c|
|
249
|
-
parse_path_to_object(path.child(c), is_within_eigenclass: is_within_eigenclass)
|
344
|
+
parse_path_to_object(path.child(c), is_within_eigenclass: is_within_eigenclass)
|
250
345
|
end.flatten
|
346
|
+
when :casgn
|
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
|
+
)]
|
251
353
|
else
|
252
354
|
if unknown_node_errors
|
253
355
|
parse_err "don't understand node type #{node.type}", node
|
@@ -259,6 +361,7 @@ module Parlour
|
|
259
361
|
|
260
362
|
# A parsed sig, not associated with a method.
|
261
363
|
class IntermediateSig < T::Struct
|
364
|
+
prop :type_parameters, T.nilable(T::Array[Symbol])
|
262
365
|
prop :overridable, T::Boolean
|
263
366
|
prop :override, T::Boolean
|
264
367
|
prop :abstract, T::Boolean
|
@@ -318,7 +421,18 @@ module Parlour
|
|
318
421
|
arg.to_a
|
319
422
|
end
|
320
423
|
|
424
|
+
# Find type parameters if they were used
|
425
|
+
type_parameters = sig_chain
|
426
|
+
.find { |(n, _)| n == :type_parameters }
|
427
|
+
&.then do |(_, a)|
|
428
|
+
a.map do |arg|
|
429
|
+
parse_err 'type parameter must be a symbol', arg if arg.type != :sym
|
430
|
+
arg.to_a[0]
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
321
434
|
IntermediateSig.new(
|
435
|
+
type_parameters: type_parameters,
|
322
436
|
overridable: overridable,
|
323
437
|
override: override,
|
324
438
|
abstract: abstract,
|
@@ -370,7 +484,7 @@ module Parlour
|
|
370
484
|
|| target != nil
|
371
485
|
|
372
486
|
parse_err 'typed attribute should have at least one name', def_node if parameters&.length == 0
|
373
|
-
|
487
|
+
|
374
488
|
kind = :attr
|
375
489
|
attr_direction = method_name.to_s.gsub('attr_', '').to_sym
|
376
490
|
def_names = T.must(parameters).map { |param| param.to_a[0].to_s }
|
@@ -389,6 +503,13 @@ module Parlour
|
|
389
503
|
return_type = this_sig.return_type
|
390
504
|
|
391
505
|
if kind == :def
|
506
|
+
# Sorbet allows a trailing blockarg that's not in the sig
|
507
|
+
if params &&
|
508
|
+
def_params.length == params.length + 1 &&
|
509
|
+
def_params[-1].type == :blockarg
|
510
|
+
def_params = def_params[0...-1]
|
511
|
+
end
|
512
|
+
|
392
513
|
parse_err 'mismatching number of arguments in sig and def', sig_block_node \
|
393
514
|
if params && def_params.length != params.length
|
394
515
|
|
@@ -399,6 +520,7 @@ module Parlour
|
|
399
520
|
parameters = params \
|
400
521
|
? zip_by(params, ->x{ x.to_a[0].to_a[0] }, def_params, ->x{ x.to_a[0] })
|
401
522
|
.map do |sig_arg, def_param|
|
523
|
+
|
402
524
|
arg_name = def_param.to_a[0]
|
403
525
|
|
404
526
|
# TODO: anonymous restarg
|
@@ -418,10 +540,11 @@ module Parlour
|
|
418
540
|
# There should only be one ever here, but future-proofing anyway
|
419
541
|
def_names.map do |def_name|
|
420
542
|
RbiGenerator::Method.new(
|
421
|
-
|
543
|
+
generator,
|
422
544
|
def_name,
|
423
545
|
parameters,
|
424
546
|
return_type,
|
547
|
+
type_parameters: this_sig.type_parameters,
|
425
548
|
override: this_sig.override,
|
426
549
|
overridable: this_sig.overridable,
|
427
550
|
abstract: this_sig.abstract,
|
@@ -437,7 +560,7 @@ module Parlour
|
|
437
560
|
|
438
561
|
parse_err "attr_#{attr_direction} sig should have non-void return", sig_block_node \
|
439
562
|
unless return_type
|
440
|
-
|
563
|
+
|
441
564
|
attr_type = return_type
|
442
565
|
when :writer
|
443
566
|
# These are special and can only have one name
|
@@ -457,7 +580,111 @@ module Parlour
|
|
457
580
|
|
458
581
|
def_names.map do |def_name|
|
459
582
|
RbiGenerator::Attribute.new(
|
460
|
-
|
583
|
+
generator,
|
584
|
+
def_name,
|
585
|
+
attr_direction,
|
586
|
+
attr_type,
|
587
|
+
class_attribute: class_method
|
588
|
+
)
|
589
|
+
end
|
590
|
+
else
|
591
|
+
raise "unknown definition kind #{kind}"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
sig { params(path: NodePath, is_within_eigenclass: T::Boolean).returns(T::Array[RbiGenerator::Method]) }
|
596
|
+
# Given a path to a method in the AST, finds the associated definition and
|
597
|
+
# parses them into methods.
|
598
|
+
# Usually this will return one method; the only exception currently is for
|
599
|
+
# attributes, where multiple can be declared in one call, e.g.
|
600
|
+
# +attr_reader :x, :y, :z+.
|
601
|
+
#
|
602
|
+
# @param [NodePath] path The sig to parse.
|
603
|
+
# @param [Boolean] is_within_eigenclass Whether the method definition this sig is
|
604
|
+
# associated with appears inside an eigenclass definition. If true, the
|
605
|
+
# returned method is made a class method. If the method definition
|
606
|
+
# is already a class method, an exception is thrown as the method will be
|
607
|
+
# a class method of the eigenclass, which Parlour can't represent.
|
608
|
+
# @return [<RbiGenerator::Method>] The parsed methods.
|
609
|
+
def parse_method_into_methods(path, is_within_eigenclass: false)
|
610
|
+
# A :def node represents a definition like "def x; end"
|
611
|
+
# A :defs node represents a definition like "def self.x; end"
|
612
|
+
def_node = path.traverse(ast)
|
613
|
+
case def_node.type
|
614
|
+
when :def
|
615
|
+
class_method = false
|
616
|
+
def_names = [def_node.to_a[0].to_s]
|
617
|
+
def_params = def_node.to_a[1].to_a
|
618
|
+
kind = :def
|
619
|
+
when :defs
|
620
|
+
parse_err 'targeted definitions on a non-self target are not supported', def_node \
|
621
|
+
unless def_node.to_a[0].type == :self
|
622
|
+
class_method = true
|
623
|
+
def_names = [def_node.to_a[1].to_s]
|
624
|
+
def_params = def_node.to_a[2].to_a
|
625
|
+
kind = :def
|
626
|
+
when :send
|
627
|
+
target, method_name, *parameters = *def_node
|
628
|
+
|
629
|
+
parse_err 'node after a sig must be a method definition', def_node \
|
630
|
+
unless [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) \
|
631
|
+
|| target != nil
|
632
|
+
|
633
|
+
parse_err 'typed attribute should have at least one name', def_node if parameters&.length == 0
|
634
|
+
|
635
|
+
kind = :attr
|
636
|
+
attr_direction = method_name.to_s.gsub('attr_', '').to_sym
|
637
|
+
def_names = T.must(parameters).map { |param| param.to_a[0].to_s }
|
638
|
+
class_method = false
|
639
|
+
else
|
640
|
+
parse_err 'node after a sig must be a method definition', def_node
|
641
|
+
end
|
642
|
+
|
643
|
+
if is_within_eigenclass
|
644
|
+
parse_err 'cannot represent multiple levels of eigenclassing', def_node if class_method
|
645
|
+
class_method = true
|
646
|
+
end
|
647
|
+
|
648
|
+
return_type = "T.untyped"
|
649
|
+
|
650
|
+
if kind == :def
|
651
|
+
parameters = def_params.map do |def_param|
|
652
|
+
arg_name = def_param.to_a[0]
|
653
|
+
|
654
|
+
# TODO: anonymous restarg
|
655
|
+
full_name = arg_name.to_s
|
656
|
+
full_name = "*#{arg_name}" if def_param.type == :restarg
|
657
|
+
full_name = "**#{arg_name}" if def_param.type == :kwrestarg
|
658
|
+
full_name = "#{arg_name}:" if def_param.type == :kwarg || def_param.type == :kwoptarg
|
659
|
+
full_name = "&#{arg_name}" if def_param.type == :blockarg
|
660
|
+
|
661
|
+
default = def_param.to_a[1] ? node_to_s(def_param.to_a[1]) : nil
|
662
|
+
type = nil
|
663
|
+
|
664
|
+
RbiGenerator::Parameter.new(full_name, type: type, default: default)
|
665
|
+
end
|
666
|
+
|
667
|
+
# There should only be one ever here, but future-proofing anyway
|
668
|
+
def_names.map do |def_name|
|
669
|
+
RbiGenerator::Method.new(
|
670
|
+
generator,
|
671
|
+
def_name,
|
672
|
+
parameters,
|
673
|
+
return_type,
|
674
|
+
class_method: class_method
|
675
|
+
)
|
676
|
+
end
|
677
|
+
elsif kind == :attr
|
678
|
+
case attr_direction
|
679
|
+
when :reader, :accessor, :writer
|
680
|
+
attr_type = return_type
|
681
|
+
else
|
682
|
+
raise "unknown attribute direction #{attr_direction}"
|
683
|
+
end
|
684
|
+
|
685
|
+
def_names.map do |def_name|
|
686
|
+
RbiGenerator::Attribute.new(
|
687
|
+
generator,
|
461
688
|
def_name,
|
462
689
|
attr_direction,
|
463
690
|
attr_type,
|
@@ -469,14 +696,166 @@ module Parlour
|
|
469
696
|
end
|
470
697
|
end
|
471
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
|
+
|
472
840
|
protected
|
473
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
|
+
|
474
853
|
sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Array[Symbol]) }
|
475
854
|
# Given a node representing a simple chain of constants (such as A or
|
476
855
|
# A::B::C), converts that node into an array of the constant names which
|
477
856
|
# are accessed. For example, A::B::C would become [:A, :B, :C].
|
478
857
|
#
|
479
|
-
# @param [Parser::AST::Node, nil] node The node to convert. This must
|
858
|
+
# @param [Parser::AST::Node, nil] node The node to convert. This must
|
480
859
|
# consist only of nested (:const) nodes.
|
481
860
|
# @return [Array<Symbol>] The chain of constant names.
|
482
861
|
def constant_names(node)
|
@@ -497,6 +876,24 @@ module Parlour
|
|
497
876
|
node.to_a[0].to_a[1] == :sig
|
498
877
|
end
|
499
878
|
|
879
|
+
sig { params(path: NodePath).returns(T::Boolean) }
|
880
|
+
# Given a path, returns a boolean indicating whether the previous sibling
|
881
|
+
# represents a call to "sig" with a block.
|
882
|
+
#
|
883
|
+
# @param [NodePath] path The path to the namespace definition.
|
884
|
+
# @return [Boolean] True if that node represents a "sig" call, false
|
885
|
+
# otherwise.
|
886
|
+
def previous_sibling_sig_node?(path)
|
887
|
+
previous_sibling = path.sibling(-1)
|
888
|
+
previous_node = previous_sibling.traverse(ast)
|
889
|
+
sig_node?(previous_node)
|
890
|
+
rescue IndexError, ArgumentError, TypeError
|
891
|
+
# `sibling` call could raise IndexError or ArgumentError if reaching into negative indices
|
892
|
+
# `traverse` call could raise TypeError if path doesn't return Parser::AST::Node
|
893
|
+
|
894
|
+
false
|
895
|
+
end
|
896
|
+
|
500
897
|
sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
|
501
898
|
# Given an AST node, returns the source code from which it was constructed.
|
502
899
|
# If the given AST node is nil, this returns nil.
|
@@ -524,13 +921,13 @@ module Parlour
|
|
524
921
|
def body_has_modifier?(node, modifier)
|
525
922
|
return false unless node
|
526
923
|
|
527
|
-
(node.type == :send && node.to_a == [nil, modifier]) ||
|
924
|
+
(node.type == :send && node.to_a == [nil, modifier]) ||
|
528
925
|
(node.type == :begin &&
|
529
926
|
node.to_a.any? { |c| c.type == :send && c.to_a == [nil, modifier] })
|
530
927
|
end
|
531
928
|
|
532
929
|
sig { params(node: Parser::AST::Node).returns([T::Array[String], T::Array[String]]) }
|
533
|
-
# Given an AST node representing the body of a class or module, returns two
|
930
|
+
# Given an AST node representing the body of a class or module, returns two
|
534
931
|
# arrays of the includes and extends contained within the body.
|
535
932
|
#
|
536
933
|
# @param [Parser::AST::Node] node The body of the namespace.
|
@@ -568,7 +965,7 @@ module Parlour
|
|
568
965
|
raise ParseError.new(buffer, range), desc
|
569
966
|
end
|
570
967
|
|
571
|
-
sig do
|
968
|
+
sig do
|
572
969
|
type_parameters(:A, :B)
|
573
970
|
.params(
|
574
971
|
a: T::Array[T.type_parameter(:A)],
|
@@ -580,7 +977,7 @@ module Parlour
|
|
580
977
|
end
|
581
978
|
# Given two arrays and functions to get a key for each item in the two
|
582
979
|
# arrays, joins the two arrays into one array of pairs by that key.
|
583
|
-
#
|
980
|
+
#
|
584
981
|
# The arrays should both be the same length, and the key functions should
|
585
982
|
# never return duplicate keys for two different items.
|
586
983
|
#
|
@@ -588,7 +985,7 @@ module Parlour
|
|
588
985
|
# @param [A -> Any] fa A function to obtain a key for any element in the
|
589
986
|
# first array.
|
590
987
|
# @param [Array<B>] b The second array.
|
591
|
-
# @param [B -> Any] fb A function to obtain a key for any element in the
|
988
|
+
# @param [B -> Any] fb A function to obtain a key for any element in the
|
592
989
|
# second array.
|
593
990
|
# @return [Array<(A, B)>] An array of pairs, where the left of the pair is
|
594
991
|
# an element from A and the right is the element from B with the
|
@@ -606,4 +1003,4 @@ module Parlour
|
|
606
1003
|
end
|
607
1004
|
end
|
608
1005
|
end
|
609
|
-
end
|
1006
|
+
end
|