steep 0.1.0.pre
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 +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +95 -0
- data/Rakefile +23 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/smoke_runner.rb +106 -0
- data/exe/steep +18 -0
- data/lib/steep.rb +33 -0
- data/lib/steep/annotation.rb +223 -0
- data/lib/steep/cli.rb +79 -0
- data/lib/steep/drivers/check.rb +141 -0
- data/lib/steep/errors.rb +207 -0
- data/lib/steep/interface.rb +280 -0
- data/lib/steep/parser.y +311 -0
- data/lib/steep/signature/class.rb +358 -0
- data/lib/steep/signature/errors.rb +78 -0
- data/lib/steep/signature/extension.rb +51 -0
- data/lib/steep/signature/interface.rb +48 -0
- data/lib/steep/source.rb +98 -0
- data/lib/steep/type_assignability.rb +362 -0
- data/lib/steep/type_construction.rb +993 -0
- data/lib/steep/type_name.rb +37 -0
- data/lib/steep/types.rb +4 -0
- data/lib/steep/types/any.rb +31 -0
- data/lib/steep/types/class.rb +27 -0
- data/lib/steep/types/instance.rb +27 -0
- data/lib/steep/types/merge.rb +32 -0
- data/lib/steep/types/name.rb +57 -0
- data/lib/steep/types/union.rb +42 -0
- data/lib/steep/types/var.rb +38 -0
- data/lib/steep/typing.rb +70 -0
- data/lib/steep/version.rb +3 -0
- data/manual/annotations.md +144 -0
- data/sig/signature.rbi +54 -0
- data/sig/types.rbi +43 -0
- data/smoke/and/a.rb +9 -0
- data/smoke/array/a.rb +22 -0
- data/smoke/block/a.rb +12 -0
- data/smoke/block/a.rbi +4 -0
- data/smoke/case/a.rb +20 -0
- data/smoke/class/a.rb +31 -0
- data/smoke/class/a.rbi +9 -0
- data/smoke/class/b.rb +7 -0
- data/smoke/class/c.rb +10 -0
- data/smoke/const/a.rb +30 -0
- data/smoke/dstr/a.rb +6 -0
- data/smoke/extension/a.rb +11 -0
- data/smoke/extension/a.rbi +8 -0
- data/smoke/extension/b.rb +12 -0
- data/smoke/extension/c.rb +9 -0
- data/smoke/hello/hello.rb +13 -0
- data/smoke/hello/hello.rbi +7 -0
- data/smoke/if/a.rb +20 -0
- data/smoke/implements/a.rb +14 -0
- data/smoke/implements/a.rbi +6 -0
- data/smoke/literal/a.rb +16 -0
- data/smoke/map/a.rb +5 -0
- data/smoke/method/a.rb +26 -0
- data/smoke/method/a.rbi +0 -0
- data/smoke/module/a.rb +21 -0
- data/smoke/module/a.rbi +7 -0
- data/smoke/module/b.rb +8 -0
- data/smoke/self/a.rb +23 -0
- data/smoke/self/a.rbi +4 -0
- data/smoke/super/a.rb +34 -0
- data/smoke/super/a.rbi +10 -0
- data/smoke/yield/a.rb +18 -0
- data/stdlib/builtin.rbi +89 -0
- data/steep.gemspec +32 -0
- metadata +214 -0
data/lib/steep/cli.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Steep
|
4
|
+
class CLI
|
5
|
+
attr_reader :argv
|
6
|
+
attr_reader :stdout
|
7
|
+
attr_reader :stdin
|
8
|
+
attr_reader :stderr
|
9
|
+
attr_reader :command
|
10
|
+
|
11
|
+
def initialize(stdout:, stdin:, stderr:, argv:)
|
12
|
+
@stdout = stdout
|
13
|
+
@stdin = stdin
|
14
|
+
@stderr = stderr
|
15
|
+
@argv = argv
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.available_commands
|
19
|
+
[:check]
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup_global_options
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_command
|
27
|
+
@command = argv.shift&.to_sym
|
28
|
+
if CLI.available_commands.include?(@command)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
stderr.puts "Unknown command: #{command}"
|
32
|
+
stderr.puts " available commands: #{CLI.available_commands.join(', ')}"
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
setup_global_options or return 1
|
39
|
+
setup_command or return 1
|
40
|
+
|
41
|
+
__send__(:"process_#{command}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_check
|
45
|
+
signature_dirs = []
|
46
|
+
verbose = false
|
47
|
+
no_builtin = false
|
48
|
+
dump_all_types = false
|
49
|
+
fallback_any_is_error = false
|
50
|
+
|
51
|
+
OptionParser.new do |opts|
|
52
|
+
opts.on("-I [PATH]") {|path| signature_dirs << Pathname(path) }
|
53
|
+
opts.on("--no-builtin") { no_builtin = true }
|
54
|
+
opts.on("--verbose") { verbose = true }
|
55
|
+
opts.on("--dump-all-types") { dump_all_types = true }
|
56
|
+
opts.on("--fallback-any-is-error") { fallback_any_is_error = true }
|
57
|
+
end.parse!(argv)
|
58
|
+
|
59
|
+
unless no_builtin
|
60
|
+
signature_dirs.unshift Pathname(__dir__).join("../../stdlib").realpath
|
61
|
+
end
|
62
|
+
|
63
|
+
if signature_dirs.empty?
|
64
|
+
signature_dirs << Pathname("sig")
|
65
|
+
end
|
66
|
+
|
67
|
+
source_paths = argv.map {|path| Pathname(path) }
|
68
|
+
if source_paths.empty?
|
69
|
+
source_paths << Pathname(".")
|
70
|
+
end
|
71
|
+
|
72
|
+
Drivers::Check.new(source_paths: source_paths, signature_dirs: signature_dirs, stdout: stdout, stderr: stderr).tap do |check|
|
73
|
+
check.verbose = verbose
|
74
|
+
check.dump_all_types = dump_all_types
|
75
|
+
check.fallback_any_is_error = fallback_any_is_error
|
76
|
+
end.run
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
class Check
|
4
|
+
attr_reader :source_paths
|
5
|
+
attr_reader :signature_dirs
|
6
|
+
attr_reader :stdout
|
7
|
+
attr_reader :stderr
|
8
|
+
|
9
|
+
attr_accessor :verbose
|
10
|
+
attr_accessor :accept_implicit_any
|
11
|
+
attr_accessor :dump_all_types
|
12
|
+
attr_accessor :fallback_any_is_error
|
13
|
+
|
14
|
+
attr_reader :labeling
|
15
|
+
|
16
|
+
def initialize(source_paths:, signature_dirs:, stdout:, stderr:)
|
17
|
+
@source_paths = source_paths
|
18
|
+
@signature_dirs = signature_dirs
|
19
|
+
@stdout = stdout
|
20
|
+
@stderr = stderr
|
21
|
+
|
22
|
+
self.verbose = false
|
23
|
+
self.accept_implicit_any = false
|
24
|
+
self.dump_all_types = false
|
25
|
+
self.fallback_any_is_error = false
|
26
|
+
|
27
|
+
@labeling = ASTUtils::Labeling.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
assignability = TypeAssignability.new do |a|
|
32
|
+
each_interface do |signature|
|
33
|
+
a.add_signature(signature)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
sources = []
|
38
|
+
each_ruby_source do |source|
|
39
|
+
sources << source
|
40
|
+
end
|
41
|
+
|
42
|
+
typing = Typing.new
|
43
|
+
|
44
|
+
sources.each do |source|
|
45
|
+
stdout.puts "Typechecking #{source.path}..." if verbose
|
46
|
+
annotations = source.annotations(block: source.node) || []
|
47
|
+
|
48
|
+
p annotations if verbose
|
49
|
+
|
50
|
+
construction = TypeConstruction.new(
|
51
|
+
assignability: assignability,
|
52
|
+
annotations: annotations,
|
53
|
+
source: source,
|
54
|
+
var_types: {},
|
55
|
+
self_type: nil,
|
56
|
+
block_context: nil,
|
57
|
+
module_context: TypeConstruction::ModuleContext.new(
|
58
|
+
instance_type: nil,
|
59
|
+
module_type: nil,
|
60
|
+
const_types: annotations.const_types
|
61
|
+
),
|
62
|
+
method_context: nil,
|
63
|
+
typing: typing,
|
64
|
+
)
|
65
|
+
construction.synthesize(source.node)
|
66
|
+
end
|
67
|
+
|
68
|
+
if dump_all_types
|
69
|
+
lines = []
|
70
|
+
|
71
|
+
typing.nodes.each_value do |node|
|
72
|
+
begin
|
73
|
+
type = typing.type_of(node: node)
|
74
|
+
lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, type]
|
75
|
+
rescue
|
76
|
+
lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, nil]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
lines.sort {|x,y| y <=> x }.reverse_each do |line|
|
81
|
+
source = line[3].loc.expression.source
|
82
|
+
stdout.puts "#{line[0]}:(#{line[2].join(",")}):(#{line[1].join(",")}):\t#{line[3].type}:\t#{line[4]}\t(#{source.split(/\n/).first})"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
typing.errors.each do |error|
|
87
|
+
next if error.is_a?(Errors::FallbackAny) && !fallback_any_is_error
|
88
|
+
stdout.puts error.to_s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def each_interface
|
93
|
+
signature_dirs.each do |path|
|
94
|
+
if path.file?
|
95
|
+
stdout.puts "Loading signature #{path}..." if verbose
|
96
|
+
Parser.parse_signature(path.read).each do |interface|
|
97
|
+
yield interface
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if path.directory?
|
102
|
+
each_file_in_dir(".rbi", path) do |file|
|
103
|
+
stdout.puts "Loading signature #{file}..." if verbose
|
104
|
+
Parser.parse_signature(file.read).each do |interface|
|
105
|
+
yield interface
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def each_ruby_source
|
113
|
+
source_paths.each do |path|
|
114
|
+
if path.file?
|
115
|
+
stdout.puts "Loading Ruby program #{path}..." if verbose
|
116
|
+
yield Source.parse(path.read, path: path.to_s, labeling: labeling)
|
117
|
+
end
|
118
|
+
|
119
|
+
if path.directory?
|
120
|
+
each_file_in_dir(".rb", path) do |file|
|
121
|
+
stdout.puts "Loading Ruby program #{file}..." if verbose
|
122
|
+
yield Source.parse(file.read, path: file.to_s, labeling: labeling)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def each_file_in_dir(suffix, path, &block)
|
129
|
+
path.children.each do |child|
|
130
|
+
if child.directory?
|
131
|
+
each_file_in_dir(suffix, child, &block)
|
132
|
+
end
|
133
|
+
|
134
|
+
if child.file? && suffix == child.extname
|
135
|
+
yield child
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/steep/errors.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
module Steep
|
2
|
+
module Errors
|
3
|
+
class Base
|
4
|
+
attr_reader :node
|
5
|
+
|
6
|
+
def initialize(node:)
|
7
|
+
@node = node
|
8
|
+
end
|
9
|
+
|
10
|
+
def location_to_str
|
11
|
+
"#{node.loc.expression.source_buffer.name}:#{node.loc.first_line}:#{node.loc.column}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class IncompatibleAssignment < Base
|
16
|
+
attr_reader :lhs_type
|
17
|
+
attr_reader :rhs_type
|
18
|
+
|
19
|
+
def initialize(node:, lhs_type:, rhs_type:)
|
20
|
+
super(node: node)
|
21
|
+
@lhs_type = lhs_type
|
22
|
+
@rhs_type = rhs_type
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{location_to_str}: IncompatibleAssignment: lhs_type=#{lhs_type}, rhs_type=#{rhs_type}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ArgumentTypeMismatch < Base
|
31
|
+
attr_reader :type
|
32
|
+
attr_reader :method
|
33
|
+
|
34
|
+
def initialize(node:, type:, method:)
|
35
|
+
super(node: node)
|
36
|
+
@type = type
|
37
|
+
@method = method
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"#{location_to_str}: ArgumentTypeMismatch: type=#{type}, method=#{method}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class NoMethod < Base
|
46
|
+
attr_reader :type
|
47
|
+
attr_reader :method
|
48
|
+
|
49
|
+
def initialize(node:, type:, method:)
|
50
|
+
super(node: node)
|
51
|
+
@type = type
|
52
|
+
@method = method
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"#{location_to_str}: NoMethodError: type=#{type}, method=#{method}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ReturnTypeMismatch < Base
|
61
|
+
attr_reader :expected
|
62
|
+
attr_reader :actual
|
63
|
+
|
64
|
+
def initialize(node:, expected:, actual:)
|
65
|
+
super(node: node)
|
66
|
+
@expected = expected
|
67
|
+
@actual = actual
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"#{location_to_str}: ReturnTypeMismatch: expected=#{expected}, actual=#{actual}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class UnexpectedBlockGiven < Base
|
76
|
+
attr_reader :method
|
77
|
+
attr_reader :type
|
78
|
+
|
79
|
+
def initialize(node:, type:, method:)
|
80
|
+
super(node: node)
|
81
|
+
@type = type
|
82
|
+
@method = method
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class BlockTypeMismatch < Base
|
87
|
+
attr_reader :expected
|
88
|
+
attr_reader :actual
|
89
|
+
|
90
|
+
def initialize(node:, expected:, actual:)
|
91
|
+
super(node: node)
|
92
|
+
@expected = expected
|
93
|
+
@actual = actual
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
"#{location_to_str}: BlockTypeMismatch: expected=#{expected}, actual=#{actual}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class BreakTypeMismatch < Base
|
102
|
+
attr_reader :expected
|
103
|
+
attr_reader :actual
|
104
|
+
|
105
|
+
def initialize(node:, expected:, actual:)
|
106
|
+
super(node: node)
|
107
|
+
@expected = expected
|
108
|
+
@actual = actual
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class MethodParameterTypeMismatch < Base
|
113
|
+
def to_s
|
114
|
+
"#{location_to_str}: MethodParameterTypeMismatch: method=#{node.children[0]}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class MethodBodyTypeMismatch < Base
|
119
|
+
attr_reader :expected
|
120
|
+
attr_reader :actual
|
121
|
+
|
122
|
+
def initialize(node:, expected:, actual:)
|
123
|
+
super(node: node)
|
124
|
+
@expected = expected
|
125
|
+
@actual = actual
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
method = case node.type
|
130
|
+
when :def
|
131
|
+
node.children[0]
|
132
|
+
when :defs
|
133
|
+
prefix = node.children[0].type == :self ? "self" : "*"
|
134
|
+
"#{prefix}.#{node.children[1]}"
|
135
|
+
end
|
136
|
+
"#{location_to_str}: MethodBodyTypeMismatch: method=#{method}, expected=#{expected}, actual=#{actual}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class UnexpectedYield < Base
|
141
|
+
def to_s
|
142
|
+
"#{location_to_str}: UnexpectedYield"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class UnexpectedSuper < Base
|
147
|
+
attr_reader :method
|
148
|
+
|
149
|
+
def initialize(node:, method:)
|
150
|
+
super(node: node)
|
151
|
+
@method = method
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_s
|
155
|
+
"#{location_to_str}: UnexpectedSuper: method=#{method}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class MethodDefinitionMissing < Base
|
160
|
+
attr_reader :module_name
|
161
|
+
attr_reader :kind
|
162
|
+
attr_reader :missing_method
|
163
|
+
|
164
|
+
def initialize(node:, module_name:, kind:, missing_method:)
|
165
|
+
super(node: node)
|
166
|
+
@module_name = module_name
|
167
|
+
@kind = kind
|
168
|
+
@missing_method = missing_method
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
method = case kind
|
173
|
+
when :instance
|
174
|
+
"#{missing_method}"
|
175
|
+
when :module
|
176
|
+
"self.#{missing_method}"
|
177
|
+
end
|
178
|
+
"#{location_to_str}: MethodDefinitionMissing: module=#{module_name}, method=#{method}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class UnexpectedDynamicMethod < Base
|
183
|
+
attr_reader :module_name
|
184
|
+
attr_reader :method_name
|
185
|
+
|
186
|
+
def initialize(node:, module_name:, method_name:)
|
187
|
+
@node = node
|
188
|
+
@module_name = module_name
|
189
|
+
@method_name = method_name
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_s
|
193
|
+
"#{location_to_str}: UnexpectedDynamicMethod: module=#{module_name}, method=#{method_name}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class FallbackAny < Base
|
198
|
+
def initialize(node:)
|
199
|
+
@node = node
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_s
|
203
|
+
"#{location_to_str}: FallbackAny"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
module Steep
|
2
|
+
class Interface
|
3
|
+
class Params
|
4
|
+
attr_reader :required
|
5
|
+
attr_reader :optional
|
6
|
+
attr_reader :rest
|
7
|
+
attr_reader :required_keywords
|
8
|
+
attr_reader :optional_keywords
|
9
|
+
attr_reader :rest_keywords
|
10
|
+
|
11
|
+
def initialize(required:, optional:, rest:, required_keywords:, optional_keywords:, rest_keywords:)
|
12
|
+
@required = required
|
13
|
+
@optional = optional
|
14
|
+
@rest = rest
|
15
|
+
@required_keywords = required_keywords
|
16
|
+
@optional_keywords = optional_keywords
|
17
|
+
@rest_keywords = rest_keywords
|
18
|
+
end
|
19
|
+
|
20
|
+
def with(required: nil, optional: nil, rest: nil, required_keywords: nil, optional_keywords: nil, rest_keywords: nil)
|
21
|
+
self.class.new(required: required || self.required,
|
22
|
+
optional: optional || self.optional,
|
23
|
+
rest: rest || self.rest,
|
24
|
+
required_keywords: required_keywords || self.required_keywords,
|
25
|
+
optional_keywords: optional_keywords || self.optional_keywords,
|
26
|
+
rest_keywords: rest_keywords || self.rest_keywords)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.empty
|
30
|
+
new(required: [], optional: [], rest: nil, required_keywords: {}, optional_keywords: {}, rest_keywords: nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
other.is_a?(self.class) &&
|
35
|
+
other.required == required &&
|
36
|
+
other.optional == optional &&
|
37
|
+
other.rest == rest &&
|
38
|
+
other.required_keywords == required_keywords &&
|
39
|
+
other.optional_keywords == optional_keywords &&
|
40
|
+
other.rest_keywords == rest_keywords
|
41
|
+
end
|
42
|
+
|
43
|
+
def flat_unnamed_params
|
44
|
+
required.map {|p| [:required, p] } + optional.map {|p| [:optional, p] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def flat_keywords
|
48
|
+
required_keywords.merge optional_keywords
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_keywords?
|
52
|
+
!required_keywords.empty? || !optional_keywords.empty? || rest_keywords
|
53
|
+
end
|
54
|
+
|
55
|
+
def each_missing_argument(args)
|
56
|
+
required.size.times do |index|
|
57
|
+
if index >= args.size
|
58
|
+
yield index
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def each_extra_argument(args)
|
64
|
+
return if rest
|
65
|
+
|
66
|
+
if has_keywords?
|
67
|
+
args = args.take(args.count - 1) if args.count > 0
|
68
|
+
end
|
69
|
+
|
70
|
+
args.size.times do |index|
|
71
|
+
if index >= required.count + optional.count
|
72
|
+
yield index
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def each_missing_keyword(args)
|
78
|
+
return unless has_keywords?
|
79
|
+
|
80
|
+
keywords, rest = extract_keywords(args)
|
81
|
+
|
82
|
+
return unless rest.empty?
|
83
|
+
|
84
|
+
required_keywords.each do |keyword, _|
|
85
|
+
yield keyword unless keywords.key?(keyword)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def each_extra_keyword(args)
|
90
|
+
return unless has_keywords?
|
91
|
+
return if rest_keywords
|
92
|
+
|
93
|
+
keywords, rest = extract_keywords(args)
|
94
|
+
|
95
|
+
return unless rest.empty?
|
96
|
+
|
97
|
+
all_keywords = flat_keywords
|
98
|
+
keywords.each do |keyword, _|
|
99
|
+
yield keyword unless all_keywords.key?(keyword)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def extract_keywords(args)
|
104
|
+
last_arg = args.last
|
105
|
+
|
106
|
+
keywords = {}
|
107
|
+
rest = []
|
108
|
+
|
109
|
+
if last_arg&.type == :hash
|
110
|
+
last_arg.children.each do |element|
|
111
|
+
case element.type
|
112
|
+
when :pair
|
113
|
+
if element.children[0].type == :sym
|
114
|
+
name = element.children[0].children[0]
|
115
|
+
keywords[name] = element.children[1]
|
116
|
+
end
|
117
|
+
when :kwsplat
|
118
|
+
rest << element.children[0]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
[keywords, rest]
|
124
|
+
end
|
125
|
+
|
126
|
+
def each_type()
|
127
|
+
if block_given?
|
128
|
+
flat_unnamed_params.each do |(_, type)|
|
129
|
+
yield type
|
130
|
+
end
|
131
|
+
flat_keywords.each do |_, type|
|
132
|
+
yield type
|
133
|
+
end
|
134
|
+
rest and yield rest
|
135
|
+
rest_keywords and yield rest_keywords
|
136
|
+
else
|
137
|
+
enum_for :each_type
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def closed?
|
142
|
+
required.all?(&:closed?) && optional.all?(&:closed?) && (!rest || rest.closed?) && required_keywords.values.all?(&:closed?) && optional_keywords.values.all?(&:closed?) && (!rest_keywords || rest_keywords.closed?)
|
143
|
+
end
|
144
|
+
|
145
|
+
def substitute(klass:, instance:, params:)
|
146
|
+
self.class.new(required: required.map {|t| t.substitute(klass: klass, instance: instance, params: params) },
|
147
|
+
optional: optional.map {|t| t.substitute(klass: klass, instance: instance, params: params) },
|
148
|
+
rest: rest&.substitute(klass: klass, instance: instance, params: params),
|
149
|
+
required_keywords: required_keywords.transform_values {|t| t.substitute(klass: klass, instance: instance, params: params) },
|
150
|
+
optional_keywords: optional_keywords.transform_values {|t| t.substitute(klass: klass, instance: instance, params: params) },
|
151
|
+
rest_keywords: rest_keywords&.substitute(klass: klass, instance: instance, params: params) )
|
152
|
+
end
|
153
|
+
|
154
|
+
def size
|
155
|
+
required.size + optional.size + (rest ? 1 : 0) + required_keywords.size + optional_keywords.size + (rest_keywords ? 1 : 0)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class MethodType
|
160
|
+
attr_reader :type_params
|
161
|
+
attr_reader :params
|
162
|
+
attr_reader :block
|
163
|
+
attr_reader :return_type
|
164
|
+
|
165
|
+
NONE = Object.new
|
166
|
+
|
167
|
+
def initialize(type_params:, params:, block:, return_type:)
|
168
|
+
@type_params = type_params
|
169
|
+
@params = params
|
170
|
+
@block = block
|
171
|
+
@return_type = return_type
|
172
|
+
end
|
173
|
+
|
174
|
+
def updated(type_params: NONE, params: NONE, block: NONE, return_type: NONE)
|
175
|
+
self.class.new(type_params: type_params.equal?(NONE) ? self.type_params : type_params,
|
176
|
+
params: params.equal?(NONE) ? self.params : params,
|
177
|
+
block: block.equal?(NONE) ? self.block : block,
|
178
|
+
return_type: return_type.equal?(NONE) ? self.return_type : return_type)
|
179
|
+
end
|
180
|
+
|
181
|
+
def ==(other)
|
182
|
+
other.is_a?(self.class) &&
|
183
|
+
other.params == params &&
|
184
|
+
other.block == block &&
|
185
|
+
other.return_type == return_type
|
186
|
+
end
|
187
|
+
|
188
|
+
def closed?
|
189
|
+
params.closed? && (!block || block.closed?) && return_type.closed?
|
190
|
+
end
|
191
|
+
|
192
|
+
def substitute(klass:, instance:, params:)
|
193
|
+
self.class.new(type_params: type_params,
|
194
|
+
params: self.params.substitute(klass: klass, instance: instance, params: params),
|
195
|
+
block: block&.substitute(klass: klass, instance: instance, params: params),
|
196
|
+
return_type: return_type.substitute(klass: klass, instance: instance, params: params))
|
197
|
+
end
|
198
|
+
|
199
|
+
def instantiate(subst:)
|
200
|
+
raise "Open method type cannot be instantiated" unless closed?
|
201
|
+
|
202
|
+
self.class.new(type_params: type_params.reject {|param| subst.key?(param) },
|
203
|
+
params: self.params.substitute(klass: nil, instance: nil, params: subst),
|
204
|
+
block: block&.substitute(klass: nil, instance: nil, params: subst),
|
205
|
+
return_type: return_type.substitute(klass: nil, instance: nil, params: subst))
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class Block
|
210
|
+
attr_reader :params
|
211
|
+
attr_reader :return_type
|
212
|
+
|
213
|
+
def initialize(params:, return_type:)
|
214
|
+
@params = params
|
215
|
+
@return_type = return_type
|
216
|
+
end
|
217
|
+
|
218
|
+
def ==(other)
|
219
|
+
other.is_a?(self.class) && other.params == params && other.return_type == return_type
|
220
|
+
end
|
221
|
+
|
222
|
+
def closed?
|
223
|
+
params.closed? && return_type.closed?
|
224
|
+
end
|
225
|
+
|
226
|
+
def substitute(klass:, instance:, params:)
|
227
|
+
self.class.new(params: self.params.substitute(klass: klass, instance: instance, params: params),
|
228
|
+
return_type: return_type.substitute(klass: klass, instance: instance, params: params))
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class Method
|
233
|
+
attr_reader :super_method
|
234
|
+
attr_reader :types
|
235
|
+
|
236
|
+
def initialize(types:, super_method:)
|
237
|
+
@types = types
|
238
|
+
@super_method = super_method
|
239
|
+
end
|
240
|
+
|
241
|
+
def ==(other)
|
242
|
+
other.is_a?(Method) && other.types == types && other.super_method == super_method
|
243
|
+
end
|
244
|
+
|
245
|
+
def closed?
|
246
|
+
types.all?(&:closed?)
|
247
|
+
end
|
248
|
+
|
249
|
+
def substitute(klass:, instance:, params:)
|
250
|
+
self.class.new(
|
251
|
+
types: types.map {|type| type.substitute(klass: klass, instance: instance, params: params) },
|
252
|
+
super_method: super_method&.substitute(klass: klass, instance: instance, params: params)
|
253
|
+
)
|
254
|
+
end
|
255
|
+
|
256
|
+
def map_types()
|
257
|
+
self.class.new(
|
258
|
+
types: types.map {|type| yield(type) },
|
259
|
+
super_method: super_method
|
260
|
+
)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
attr_reader :name
|
265
|
+
attr_reader :methods
|
266
|
+
|
267
|
+
def initialize(name:, methods:)
|
268
|
+
@name = name
|
269
|
+
@methods = methods
|
270
|
+
end
|
271
|
+
|
272
|
+
def closed?
|
273
|
+
methods.values.all?(&:closed?)
|
274
|
+
end
|
275
|
+
|
276
|
+
def ==(other)
|
277
|
+
other.is_a?(self.class) && other.name == name && other.methods == methods
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|