steep 0.1.0.pre2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +1 -1
- data/README.md +146 -33
- data/bin/smoke_runner.rb +43 -10
- data/lib/steep/ast/annotation/collection.rb +93 -0
- data/lib/steep/ast/annotation.rb +131 -0
- data/lib/steep/ast/buffer.rb +47 -0
- data/lib/steep/ast/location.rb +82 -0
- data/lib/steep/ast/method_type.rb +116 -0
- data/lib/steep/ast/signature/class.rb +33 -0
- data/lib/steep/ast/signature/const.rb +17 -0
- data/lib/steep/ast/signature/env.rb +123 -0
- data/lib/steep/ast/signature/extension.rb +21 -0
- data/lib/steep/ast/signature/gvar.rb +17 -0
- data/lib/steep/ast/signature/interface.rb +31 -0
- data/lib/steep/ast/signature/members.rb +71 -0
- data/lib/steep/ast/signature/module.rb +21 -0
- data/lib/steep/ast/type_params.rb +13 -0
- data/lib/steep/ast/types/any.rb +39 -0
- data/lib/steep/ast/types/bot.rb +39 -0
- data/lib/steep/ast/types/class.rb +35 -0
- data/lib/steep/ast/types/helper.rb +21 -0
- data/lib/steep/ast/types/instance.rb +39 -0
- data/lib/steep/ast/types/intersection.rb +74 -0
- data/lib/steep/ast/types/name.rb +124 -0
- data/lib/steep/ast/types/self.rb +39 -0
- data/lib/steep/ast/types/top.rb +39 -0
- data/lib/steep/ast/types/union.rb +74 -0
- data/lib/steep/ast/types/var.rb +57 -0
- data/lib/steep/ast/types/void.rb +35 -0
- data/lib/steep/cli.rb +28 -1
- data/lib/steep/drivers/annotations.rb +32 -0
- data/lib/steep/drivers/check.rb +53 -77
- data/lib/steep/drivers/scaffold.rb +303 -0
- data/lib/steep/drivers/utils/each_signature.rb +66 -0
- data/lib/steep/drivers/utils/validator.rb +115 -0
- data/lib/steep/drivers/validate.rb +39 -0
- data/lib/steep/errors.rb +291 -19
- data/lib/steep/interface/abstract.rb +44 -0
- data/lib/steep/interface/builder.rb +470 -0
- data/lib/steep/interface/instantiated.rb +126 -0
- data/lib/steep/interface/ivar_chain.rb +26 -0
- data/lib/steep/interface/method.rb +60 -0
- data/lib/steep/{interface.rb → interface/method_type.rb} +111 -100
- data/lib/steep/interface/substitution.rb +65 -0
- data/lib/steep/module_name.rb +116 -0
- data/lib/steep/parser.rb +1314 -814
- data/lib/steep/parser.y +536 -175
- data/lib/steep/source.rb +220 -25
- data/lib/steep/subtyping/check.rb +673 -0
- data/lib/steep/subtyping/constraints.rb +275 -0
- data/lib/steep/subtyping/relation.rb +41 -0
- data/lib/steep/subtyping/result.rb +126 -0
- data/lib/steep/subtyping/trace.rb +48 -0
- data/lib/steep/subtyping/variable_occurrence.rb +49 -0
- data/lib/steep/subtyping/variable_variance.rb +69 -0
- data/lib/steep/type_construction.rb +1630 -524
- data/lib/steep/type_inference/block_params.rb +100 -0
- data/lib/steep/type_inference/constant_env.rb +55 -0
- data/lib/steep/type_inference/send_args.rb +222 -0
- data/lib/steep/type_inference/type_env.rb +226 -0
- data/lib/steep/type_name.rb +27 -7
- data/lib/steep/typing.rb +4 -0
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +71 -16
- data/smoke/and/a.rb +4 -2
- data/smoke/array/a.rb +4 -5
- data/smoke/array/b.rb +4 -4
- data/smoke/block/a.rb +2 -2
- data/smoke/block/a.rbi +2 -0
- data/smoke/block/b.rb +15 -0
- data/smoke/case/a.rb +3 -3
- data/smoke/class/a.rb +3 -3
- data/smoke/class/b.rb +0 -2
- data/smoke/class/d.rb +2 -2
- data/smoke/class/e.rb +1 -1
- data/smoke/class/f.rb +2 -2
- data/smoke/class/g.rb +8 -0
- data/smoke/const/a.rb +3 -3
- data/smoke/dstr/a.rb +1 -1
- data/smoke/ensure/a.rb +22 -0
- data/smoke/enumerator/a.rb +6 -6
- data/smoke/enumerator/b.rb +22 -0
- data/smoke/extension/a.rb +2 -2
- data/smoke/extension/b.rb +3 -3
- data/smoke/extension/c.rb +1 -1
- data/smoke/hello/hello.rb +2 -2
- data/smoke/if/a.rb +4 -2
- data/smoke/kwbegin/a.rb +8 -0
- data/smoke/literal/a.rb +5 -5
- data/smoke/method/a.rb +5 -5
- data/smoke/method/a.rbi +4 -0
- data/smoke/method/b.rb +29 -0
- data/smoke/module/a.rb +3 -3
- data/smoke/module/a.rbi +9 -0
- data/smoke/module/b.rb +2 -2
- data/smoke/module/c.rb +1 -1
- data/smoke/module/d.rb +5 -0
- data/smoke/module/e.rb +13 -0
- data/smoke/module/f.rb +13 -0
- data/smoke/rescue/a.rb +62 -0
- data/smoke/super/a.rb +2 -2
- data/smoke/type_case/a.rb +35 -0
- data/smoke/yield/a.rb +2 -2
- data/stdlib/builtin.rbi +463 -24
- data/steep.gemspec +3 -2
- metadata +91 -29
- data/lib/steep/annotation.rb +0 -223
- data/lib/steep/signature/class.rb +0 -450
- data/lib/steep/signature/extension.rb +0 -51
- data/lib/steep/signature/interface.rb +0 -49
- data/lib/steep/types/any.rb +0 -31
- data/lib/steep/types/class.rb +0 -27
- data/lib/steep/types/instance.rb +0 -27
- data/lib/steep/types/merge.rb +0 -32
- data/lib/steep/types/name.rb +0 -57
- data/lib/steep/types/union.rb +0 -42
- data/lib/steep/types/var.rb +0 -38
- data/lib/steep/types.rb +0 -4
data/lib/steep/drivers/check.rb
CHANGED
@@ -13,6 +13,8 @@ module Steep
|
|
13
13
|
|
14
14
|
attr_reader :labeling
|
15
15
|
|
16
|
+
include Utils::EachSignature
|
17
|
+
|
16
18
|
def initialize(source_paths:, signature_dirs:, stdout:, stderr:)
|
17
19
|
@source_paths = source_paths
|
18
20
|
@signature_dirs = signature_dirs
|
@@ -28,45 +30,67 @@ module Steep
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def run
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
Steep.logger.level = Logger::DEBUG if verbose
|
34
|
+
|
35
|
+
env = AST::Signature::Env.new
|
36
|
+
|
37
|
+
each_signature(signature_dirs, verbose) do |signature|
|
38
|
+
env.add signature
|
39
|
+
end
|
40
|
+
|
41
|
+
builder = Interface::Builder.new(signatures: env)
|
42
|
+
check = Subtyping::Check.new(builder: builder)
|
43
|
+
|
44
|
+
validator = Utils::Validator.new(stdout: stdout, stderr: stderr, verbose: verbose)
|
45
|
+
|
46
|
+
validated = validator.run(env: env, builder: builder, check: check) do |sig|
|
47
|
+
stderr.puts "Validating #{sig.name} (#{sig.location.name}:#{sig.location.start_line})..." if verbose
|
35
48
|
end
|
36
49
|
|
37
|
-
|
38
|
-
|
50
|
+
unless validated
|
51
|
+
return 1
|
39
52
|
end
|
40
53
|
|
41
54
|
sources = []
|
42
|
-
each_ruby_source do |source|
|
55
|
+
each_ruby_source(source_paths, verbose) do |source|
|
43
56
|
sources << source
|
44
57
|
end
|
45
58
|
|
46
59
|
typing = Typing.new
|
47
60
|
|
48
61
|
sources.each do |source|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
annotations: annotations,
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
62
|
+
Steep.logger.tagged source.path do
|
63
|
+
Steep.logger.debug "Typechecking..."
|
64
|
+
annotations = source.annotations(block: source.node) || []
|
65
|
+
|
66
|
+
pp annotations if verbose
|
67
|
+
|
68
|
+
const_env = TypeInference::ConstantEnv.new(builder: check.builder, current_namespace: nil)
|
69
|
+
type_env = TypeInference::TypeEnv.build(annotations: annotations,
|
70
|
+
subtyping: check,
|
71
|
+
const_env: const_env,
|
72
|
+
signatures: check.builder.signatures)
|
73
|
+
|
74
|
+
construction = TypeConstruction.new(
|
75
|
+
checker: check,
|
76
|
+
annotations: annotations,
|
77
|
+
source: source,
|
78
|
+
self_type: AST::Types::Name.new_instance(name: "::Object"),
|
79
|
+
block_context: nil,
|
80
|
+
module_context: TypeConstruction::ModuleContext.new(
|
81
|
+
instance_type: nil,
|
82
|
+
module_type: nil,
|
83
|
+
implement_name: nil,
|
84
|
+
current_namespace: nil,
|
85
|
+
const_env: const_env
|
86
|
+
),
|
87
|
+
method_context: nil,
|
88
|
+
typing: typing,
|
89
|
+
break_context: nil,
|
90
|
+
type_env: type_env
|
91
|
+
)
|
92
|
+
construction.synthesize(source.node)
|
93
|
+
end
|
70
94
|
end
|
71
95
|
|
72
96
|
if dump_all_types
|
@@ -89,55 +113,7 @@ module Steep
|
|
89
113
|
|
90
114
|
typing.errors.each do |error|
|
91
115
|
next if error.is_a?(Errors::FallbackAny) && !fallback_any_is_error
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def each_interface
|
97
|
-
signature_dirs.each do |path|
|
98
|
-
if path.file?
|
99
|
-
stdout.puts "Loading signature #{path}..." if verbose
|
100
|
-
Parser.parse_signature(path.read).each do |interface|
|
101
|
-
yield interface
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
if path.directory?
|
106
|
-
each_file_in_dir(".rbi", path) do |file|
|
107
|
-
stdout.puts "Loading signature #{file}..." if verbose
|
108
|
-
Parser.parse_signature(file.read).each do |interface|
|
109
|
-
yield interface
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def each_ruby_source
|
117
|
-
source_paths.each do |path|
|
118
|
-
if path.file?
|
119
|
-
stdout.puts "Loading Ruby program #{path}..." if verbose
|
120
|
-
yield Source.parse(path.read, path: path.to_s, labeling: labeling)
|
121
|
-
end
|
122
|
-
|
123
|
-
if path.directory?
|
124
|
-
each_file_in_dir(".rb", path) do |file|
|
125
|
-
stdout.puts "Loading Ruby program #{file}..." if verbose
|
126
|
-
yield Source.parse(file.read, path: file.to_s, labeling: labeling)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def each_file_in_dir(suffix, path, &block)
|
133
|
-
path.children.each do |child|
|
134
|
-
if child.directory?
|
135
|
-
each_file_in_dir(suffix, child, &block)
|
136
|
-
end
|
137
|
-
|
138
|
-
if child.file? && suffix == child.extname
|
139
|
-
yield child
|
140
|
-
end
|
116
|
+
error.print_to stdout
|
141
117
|
end
|
142
118
|
end
|
143
119
|
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
class Scaffold
|
4
|
+
attr_reader :source_paths
|
5
|
+
attr_reader :stdout
|
6
|
+
attr_reader :stderr
|
7
|
+
attr_reader :labeling
|
8
|
+
|
9
|
+
include Utils::EachSignature
|
10
|
+
|
11
|
+
def initialize(source_paths:, stdout:, stderr:)
|
12
|
+
@source_paths = source_paths
|
13
|
+
@stdout = stdout
|
14
|
+
@stderr = stderr
|
15
|
+
|
16
|
+
@labeling = ASTUtils::Labeling.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
each_ruby_source(source_paths, false) do |source|
|
21
|
+
Generator.new(source: source, stderr: stderr).write(io: stdout)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Generator
|
26
|
+
class Module
|
27
|
+
attr_reader :name
|
28
|
+
attr_reader :methods
|
29
|
+
attr_reader :singleton_methods
|
30
|
+
attr_reader :ivars
|
31
|
+
attr_reader :kind
|
32
|
+
|
33
|
+
def initialize(name:, kind:)
|
34
|
+
@kind = kind
|
35
|
+
@name = name
|
36
|
+
@ivars = {}
|
37
|
+
@methods = {}
|
38
|
+
@singleton_methods = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def class?
|
42
|
+
kind == :class
|
43
|
+
end
|
44
|
+
|
45
|
+
def module?
|
46
|
+
kind == :module
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :source
|
51
|
+
attr_reader :modules
|
52
|
+
attr_reader :constants
|
53
|
+
attr_reader :stderr
|
54
|
+
|
55
|
+
def initialize(source:, stderr:)
|
56
|
+
@source = source
|
57
|
+
@stderr = stderr
|
58
|
+
@modules = []
|
59
|
+
@constants = {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def write(io:)
|
63
|
+
generate(source.node, current_path: [])
|
64
|
+
|
65
|
+
modules.each do |mod|
|
66
|
+
unless mod.methods.empty? && mod.singleton_methods.empty?
|
67
|
+
io.puts "#{mod.kind} #{mod.name}"
|
68
|
+
|
69
|
+
mod.ivars.each do |name, type|
|
70
|
+
io.puts " #{name}: #{type}"
|
71
|
+
end
|
72
|
+
|
73
|
+
mod.methods.each do |name, type|
|
74
|
+
io.puts " def #{name}: #{type}"
|
75
|
+
end
|
76
|
+
|
77
|
+
mod.singleton_methods.each do |name, type|
|
78
|
+
io.puts " def self.#{name}: #{type}"
|
79
|
+
end
|
80
|
+
|
81
|
+
io.puts "end"
|
82
|
+
io.puts
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
constants.each do |name, ty|
|
87
|
+
io.puts "#{name}: #{ty}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def module_name(name)
|
92
|
+
if name.type == :const
|
93
|
+
prefix = name.children[0]
|
94
|
+
if prefix
|
95
|
+
"#{module_name(prefix)}::#{name.children[1]}"
|
96
|
+
else
|
97
|
+
name.children[1].to_s
|
98
|
+
end
|
99
|
+
else
|
100
|
+
stderr.puts "Unexpected node for class name: #{name}"
|
101
|
+
return "____"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def full_name(current_path, name)
|
106
|
+
(current_path + [name]).join("::")
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate(node, current_path:, current_module: nil, is_instance_method: false)
|
110
|
+
case node.type
|
111
|
+
when :module
|
112
|
+
name = module_name(node.children[0])
|
113
|
+
mod = Module.new(name: full_name(current_path, name), kind: :module)
|
114
|
+
modules << mod
|
115
|
+
|
116
|
+
if node.children[1]
|
117
|
+
generate(node.children[1],
|
118
|
+
current_path: current_path + [name],
|
119
|
+
current_module: mod)
|
120
|
+
end
|
121
|
+
|
122
|
+
when :class
|
123
|
+
name = module_name(node.children[0])
|
124
|
+
klass = Module.new(name: full_name(current_path, name), kind: :class)
|
125
|
+
modules << klass
|
126
|
+
|
127
|
+
if node.children[2]
|
128
|
+
generate(node.children[2],
|
129
|
+
current_path: current_path + [name],
|
130
|
+
current_module: klass)
|
131
|
+
end
|
132
|
+
|
133
|
+
when :def
|
134
|
+
name, args, body = node.children
|
135
|
+
|
136
|
+
if current_module
|
137
|
+
current_module.methods[name] = "(#{arg_types(args)}) -> #{guess_type(body)}"
|
138
|
+
end
|
139
|
+
|
140
|
+
if body
|
141
|
+
generate(body,
|
142
|
+
current_path: current_path,
|
143
|
+
current_module: current_module,
|
144
|
+
is_instance_method: true)
|
145
|
+
end
|
146
|
+
|
147
|
+
when :ivar, :ivasgn
|
148
|
+
name = node.children[0]
|
149
|
+
|
150
|
+
if current_module && is_instance_method
|
151
|
+
current_module.ivars[name] = guess_type(node.children[1])
|
152
|
+
end
|
153
|
+
|
154
|
+
each_child_node(node) do |child|
|
155
|
+
generate(child,
|
156
|
+
current_path: current_path,
|
157
|
+
current_module: current_module,
|
158
|
+
is_instance_method: is_instance_method)
|
159
|
+
end
|
160
|
+
|
161
|
+
when :defs
|
162
|
+
if node.children[0].type == :self
|
163
|
+
_, name, args, body = node.children
|
164
|
+
|
165
|
+
if current_module
|
166
|
+
current_module.singleton_methods[name] = "(#{arg_types(args)}) -> #{guess_type(body)}"
|
167
|
+
end
|
168
|
+
|
169
|
+
if body
|
170
|
+
generate(body,
|
171
|
+
current_path: current_path,
|
172
|
+
current_module: current_module,
|
173
|
+
is_instance_method: false)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
when :casgn
|
178
|
+
if node.children[0]
|
179
|
+
stderr.puts "Unexpected casgn: #{node}, #{node.loc.line}"
|
180
|
+
end
|
181
|
+
|
182
|
+
constants[full_name(current_path, node.children[1])] = guess_type(node.children[2])
|
183
|
+
|
184
|
+
if node.children[2]
|
185
|
+
generate(node.children[2],
|
186
|
+
current_path: current_path,
|
187
|
+
current_module: current_module,
|
188
|
+
is_instance_method: is_instance_method)
|
189
|
+
end
|
190
|
+
|
191
|
+
else
|
192
|
+
each_child_node(node) do |child|
|
193
|
+
generate(child, current_path: current_path, current_module: current_module, is_instance_method: is_instance_method)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def guess_type(node)
|
199
|
+
return "any" unless node
|
200
|
+
case node.type
|
201
|
+
when :false, :true
|
202
|
+
"_Boolean"
|
203
|
+
when :int
|
204
|
+
"Integer"
|
205
|
+
when :float
|
206
|
+
"Float"
|
207
|
+
when :complex
|
208
|
+
"Complex"
|
209
|
+
when :rational
|
210
|
+
"Rational"
|
211
|
+
when :str, :dstr, :xstr
|
212
|
+
"String"
|
213
|
+
when :sym, :dsym
|
214
|
+
"Symbol"
|
215
|
+
when :regexp
|
216
|
+
"Regexp"
|
217
|
+
when :array
|
218
|
+
"Array<any>"
|
219
|
+
when :hash
|
220
|
+
"Hash<any, any>"
|
221
|
+
when :irange, :erange
|
222
|
+
"Range<any>"
|
223
|
+
when :lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn
|
224
|
+
guess_type(node.children.last)
|
225
|
+
when :send
|
226
|
+
if node.children[1] == :[]=
|
227
|
+
guess_type(node.children.last)
|
228
|
+
else
|
229
|
+
"any"
|
230
|
+
end
|
231
|
+
when :begin
|
232
|
+
# should support shortcut return?
|
233
|
+
guess_type(node.children.last)
|
234
|
+
when :return
|
235
|
+
children = node.children
|
236
|
+
if children.size == 1
|
237
|
+
guess_type(node.children.last)
|
238
|
+
else
|
239
|
+
"Array<any>" # or Tuple or any?
|
240
|
+
end
|
241
|
+
when :if
|
242
|
+
children = node.children
|
243
|
+
if children[2]
|
244
|
+
ty1 = guess_type(children[1])
|
245
|
+
ty2 = guess_type(children[2])
|
246
|
+
if ty1 == ty2
|
247
|
+
ty1
|
248
|
+
else
|
249
|
+
"any"
|
250
|
+
end
|
251
|
+
else
|
252
|
+
"void" # assuming no-else if statement implies void
|
253
|
+
end
|
254
|
+
when :while, :until, :while_post, :until_post, :for
|
255
|
+
"void"
|
256
|
+
when :case
|
257
|
+
children = node.children
|
258
|
+
if children.last
|
259
|
+
ty = guess_type(children.last)
|
260
|
+
children[1..-2].each do |child|
|
261
|
+
return "any" if ty != guess_type(child.children.last)
|
262
|
+
end
|
263
|
+
ty
|
264
|
+
else
|
265
|
+
"any"
|
266
|
+
end
|
267
|
+
when :masgn
|
268
|
+
"void" # assuming masgn implies void
|
269
|
+
else
|
270
|
+
"any"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def each_child_node(node, &block)
|
275
|
+
node.children.each do |child|
|
276
|
+
if child.is_a?(::AST::Node)
|
277
|
+
yield child
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def arg_types(args)
|
283
|
+
args.children.map do |arg|
|
284
|
+
case arg.type
|
285
|
+
when :arg
|
286
|
+
"any"
|
287
|
+
when :optarg
|
288
|
+
"?#{guess_type(arg.children[1])}"
|
289
|
+
when :restarg
|
290
|
+
"*any"
|
291
|
+
when :kwarg
|
292
|
+
"#{arg.children.first.name}: any"
|
293
|
+
when :kwoptarg
|
294
|
+
"?#{arg.children.first.name}: #{guess_type(arg.children[1])}"
|
295
|
+
when :kwrestarg
|
296
|
+
"**any"
|
297
|
+
end
|
298
|
+
end.compact.join(", ")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
module Utils
|
4
|
+
module EachSignature
|
5
|
+
def each_signature(signature_dirs, verbose)
|
6
|
+
signature_dirs.each do |path|
|
7
|
+
if path.file?
|
8
|
+
stderr.puts "Loading signature #{path}..." if verbose
|
9
|
+
Parser.parse_signature(path.read, name: path).each do |signature|
|
10
|
+
yield signature
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if path.directory?
|
15
|
+
each_file_in_dir(".rbi", path) do |file|
|
16
|
+
stderr.puts "Loading signature #{file}..." if verbose
|
17
|
+
Parser.parse_signature(file.read, name: file).each do |signature|
|
18
|
+
yield signature
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def each_ruby_source(source_paths, verbose)
|
26
|
+
each_ruby_file source_paths do |file|
|
27
|
+
begin
|
28
|
+
stdout.puts "Loading Ruby program #{file}..." if verbose
|
29
|
+
if (source = Source.parse(file.read, path: file.to_s, labeling: labeling))
|
30
|
+
yield source
|
31
|
+
end
|
32
|
+
rescue => exn
|
33
|
+
Steep.logger.error "Error occured on parsing #{file}: #{exn.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def each_ruby_file(source_paths)
|
39
|
+
source_paths.each do |path|
|
40
|
+
if path.file?
|
41
|
+
yield path
|
42
|
+
end
|
43
|
+
|
44
|
+
if path.directory?
|
45
|
+
each_file_in_dir(".rb", path) do |file|
|
46
|
+
yield file
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def each_file_in_dir(suffix, path, &block)
|
53
|
+
path.children.each do |child|
|
54
|
+
if child.directory?
|
55
|
+
each_file_in_dir(suffix, child, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
if child.file? && suffix == child.extname
|
59
|
+
yield child
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
module Utils
|
4
|
+
class Validator
|
5
|
+
attr_reader :stdout
|
6
|
+
attr_reader :stderr
|
7
|
+
attr_reader :verbose
|
8
|
+
|
9
|
+
def initialize(stdout:, stderr:, verbose:)
|
10
|
+
@stdout = stdout
|
11
|
+
@stderr = stderr
|
12
|
+
@verbose = verbose
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(env:, builder:, check:)
|
16
|
+
result = true
|
17
|
+
|
18
|
+
env.each do |sig|
|
19
|
+
yield sig if block_given?
|
20
|
+
|
21
|
+
case sig
|
22
|
+
when AST::Signature::Interface
|
23
|
+
yield_self do
|
24
|
+
instance_name = TypeName::Interface.new(name: sig.name)
|
25
|
+
instance_interface = builder.build(instance_name)
|
26
|
+
|
27
|
+
args = instance_interface.params.map {|var| AST::Types::Var.fresh(var) }
|
28
|
+
instance_type = AST::Types::Name.new_interface(name: sig.name, args: args)
|
29
|
+
|
30
|
+
instance_interface.instantiate(type: instance_type,
|
31
|
+
args: args,
|
32
|
+
instance_type: instance_type,
|
33
|
+
module_type: nil).validate(check)
|
34
|
+
end
|
35
|
+
|
36
|
+
when AST::Signature::Module
|
37
|
+
yield_self do
|
38
|
+
instance_name = TypeName::Instance.new(name: sig.name)
|
39
|
+
instance_interface = builder.build(instance_name)
|
40
|
+
instance_args = instance_interface.params.map {|var| AST::Types::Var.fresh(var) }
|
41
|
+
|
42
|
+
module_name = TypeName::Module.new(name: sig.name)
|
43
|
+
module_interface = builder.build(module_name)
|
44
|
+
module_args = module_interface.params.map {|var| AST::Types::Var.fresh(var) }
|
45
|
+
|
46
|
+
instance_type = AST::Types::Name.new_instance(name: sig.name, args: instance_args)
|
47
|
+
module_type = AST::Types::Name.new_module(name: sig.name, args: module_args)
|
48
|
+
|
49
|
+
stdout.puts "👀 Validating instance methods..." if verbose
|
50
|
+
instance_interface.instantiate(type: instance_type,
|
51
|
+
args: instance_args,
|
52
|
+
instance_type: instance_type,
|
53
|
+
module_type: module_type).validate(check)
|
54
|
+
|
55
|
+
stdout.puts "👀 Validating class methods..." if verbose
|
56
|
+
module_interface.instantiate(type: module_type,
|
57
|
+
args: module_args,
|
58
|
+
instance_type: instance_type,
|
59
|
+
module_type: module_type).validate(check)
|
60
|
+
end
|
61
|
+
|
62
|
+
when AST::Signature::Class
|
63
|
+
yield_self do
|
64
|
+
instance_name = TypeName::Instance.new(name: sig.name)
|
65
|
+
instance_interface = builder.build(instance_name)
|
66
|
+
instance_args = instance_interface.params.map {|var| AST::Types::Var.fresh(var) }
|
67
|
+
|
68
|
+
module_name = TypeName::Class.new(name: sig.name, constructor: nil)
|
69
|
+
module_interface = builder.build(module_name)
|
70
|
+
module_args = module_interface.params.map {|var| AST::Types::Var.fresh(var) }
|
71
|
+
|
72
|
+
instance_type = AST::Types::Name.new_instance(name: sig.name, args: instance_args)
|
73
|
+
module_type = AST::Types::Name.new_class(name: sig.name, args: module_args, constructor: nil)
|
74
|
+
|
75
|
+
stdout.puts "👀 Validating instance methods..." if verbose
|
76
|
+
instance_interface.instantiate(type: instance_type,
|
77
|
+
args: instance_args,
|
78
|
+
instance_type: instance_type,
|
79
|
+
module_type: module_type).validate(check)
|
80
|
+
|
81
|
+
stdout.puts "👀 Validating class methods..." if verbose
|
82
|
+
module_interface.instantiate(type: module_type,
|
83
|
+
args: module_args,
|
84
|
+
instance_type: instance_type,
|
85
|
+
module_type: module_type).validate(check)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
rescue Interface::Instantiated::InvalidMethodOverrideError => exn
|
90
|
+
result = false
|
91
|
+
stdout.puts "😱 #{exn.message}"
|
92
|
+
exn.result.trace.each do |s, t|
|
93
|
+
case s
|
94
|
+
when Interface::Method
|
95
|
+
stdout.puts " #{s.name}(#{s.type_name}) <: #{t.name}(#{t.type_name})"
|
96
|
+
when Interface::MethodType
|
97
|
+
stdout.puts " #{s.location.source} <: #{t.location.source} (#{s.location.name}:#{s.location.start_line})"
|
98
|
+
else
|
99
|
+
stdout.puts " #{s} <: #{t}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
stdout.puts " 🚨 #{exn.result.error.message}"
|
103
|
+
|
104
|
+
rescue Interface::Instantiated::InvalidIvarOverrideError => exn
|
105
|
+
result = false
|
106
|
+
stdout.puts "😱 #{exn.message}"
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
class Validate
|
4
|
+
attr_reader :signature_dirs
|
5
|
+
attr_reader :stdout
|
6
|
+
attr_reader :stderr
|
7
|
+
attr_accessor :verbose
|
8
|
+
|
9
|
+
def initialize(signature_dirs:, stdout:, stderr:)
|
10
|
+
@signature_dirs = signature_dirs
|
11
|
+
@stdout = stdout
|
12
|
+
@stderr = stderr
|
13
|
+
|
14
|
+
self.verbose = false
|
15
|
+
end
|
16
|
+
|
17
|
+
include Utils::EachSignature
|
18
|
+
|
19
|
+
def run
|
20
|
+
Steep.logger.level = Logger::DEBUG if verbose
|
21
|
+
|
22
|
+
env = AST::Signature::Env.new
|
23
|
+
|
24
|
+
each_signature(signature_dirs, verbose) do |signature|
|
25
|
+
env.add signature
|
26
|
+
end
|
27
|
+
|
28
|
+
builder = Interface::Builder.new(signatures: env)
|
29
|
+
check = Subtyping::Check.new(builder: builder)
|
30
|
+
|
31
|
+
validator = Utils::Validator.new(stdout: stdout, stderr: stderr, verbose: verbose)
|
32
|
+
|
33
|
+
validator.run(env: env, builder: builder, check: check) do |sig|
|
34
|
+
stderr.puts "Validating #{sig.name} (#{sig.location.name}:#{sig.location.start_line})..." if verbose
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|