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