yadriggy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ module Yadriggy
4
+
5
+ # A helper class for pretty printing.
6
+ #
7
+ class Printer
8
+ # @param [Integer] indent the indent size. The default value is 2.
9
+ def initialize(indent=2)
10
+ @text = ''
11
+ @level = 0
12
+ @linebreak = false
13
+ @indent = ' ' * indent
14
+ end
15
+
16
+ # Returns the output stream.
17
+ #
18
+ def output()
19
+ add_newline if @linebreak
20
+ @text
21
+ end
22
+
23
+ # Increase the indentation level.
24
+ #
25
+ def down
26
+ @level += 1
27
+ add_newline
28
+ end
29
+
30
+ # Decrease the indentation level.
31
+ #
32
+ def up
33
+ @level -= 1
34
+ add_newline
35
+ end
36
+
37
+ # Starts a new line.
38
+ #
39
+ def nl
40
+ @linebreak = true
41
+ end
42
+
43
+ # Prints the text. If `code` is `:nl`, a line break is printed.
44
+ #
45
+ # @param [String|:nil] code the text.
46
+ def << (code)
47
+ add_newline if @linebreak
48
+ if code == :nl
49
+ @linebreak = true
50
+ else
51
+ @text << code.to_s
52
+ end
53
+ self
54
+ end
55
+
56
+ private
57
+
58
+ def add_newline()
59
+ @text << "\n"
60
+ @level.times { @text << @indent }
61
+ @linebreak = false
62
+ end
63
+ end
64
+
65
+ # Pretty printer to a file.
66
+ #
67
+ class FilePrinter < Printer
68
+ # @return [String] the file name.
69
+ attr_reader :file_name
70
+
71
+ # @param [String] file_name the file name.
72
+ def initialize(file_name)
73
+ super()
74
+ @text = File.open(file_name, 'w')
75
+ @file_name = file_name
76
+ end
77
+
78
+ def close
79
+ @text.close
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,468 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy/typecheck'
4
+ require 'set'
5
+
6
+ module Yadriggy
7
+ # Type checker for Ruby
8
+ #
9
+ class RubyTypeChecker < TypeChecker
10
+ def initialize(syntax=nil)
11
+ super()
12
+ @syntax = syntax
13
+ @referred_objects = Set.new
14
+ end
15
+
16
+ # @return [Set<Object>] all the constants that the type-checked code has
17
+ # referred to. Numbers and classes are excluced.
18
+ def references()
19
+ @referred_objects
20
+ end
21
+
22
+ # Makes the references set empty. The references set is a set of constants
23
+ # returned by {#references}.
24
+ #
25
+ def clear_references
26
+ @referred_objects = Set.new
27
+ end
28
+
29
+ # Typing rules
30
+ #
31
+ # Every rule returns a Ruby class or a Type object.
32
+ # When the given AST is a free variable, the type checker captures
33
+ # the current value of that variable and gives the value type to that AST.
34
+
35
+ rule(Assign) do
36
+ rtype = type(ast.right)
37
+ ltype = type(ast.left)
38
+ if ast.op != :'=' # if op is += etc.
39
+ LocalVarType.role(ltype)&.definition = ast.left
40
+ ltype
41
+ elsif ast.left.is_a?(IdentifierOrCall)
42
+ vtype = type_env.bound_name?(ast.left)
43
+ if vtype.nil?
44
+ bind_local_var(type_env, ast.left, rtype)
45
+ else
46
+ type_assert(rtype <= vtype, 'invalid assignment')
47
+ LocalVarType.role(vtype)&.definition = ast.left
48
+ vtype
49
+ end
50
+ else
51
+ ltype
52
+ end
53
+ end
54
+
55
+ rule(Name) do
56
+ get_name_type(ast, type_env)
57
+ end
58
+
59
+ rule(Const) do
60
+ type = get_name_type(ast, type_env)
61
+ unless InstanceType.role(type).nil?
62
+ obj = type.object
63
+ unless obj.is_a?(Numeric) || obj.is_a?(Module)
64
+ @referred_objects << obj
65
+ end
66
+ end
67
+ type
68
+ end
69
+
70
+ # Gets the type of a given name, which may be a local variable
71
+ # or a free variable.
72
+ #
73
+ # @param [Name] name_ast an AST.
74
+ # @param [TypeEnv] tenv a type environment.
75
+ def get_name_type(name_ast, tenv)
76
+ type = tenv.bound_name?(name_ast)
77
+ if type
78
+ type
79
+ else
80
+ v = name_ast.value
81
+ if v == Undef
82
+ DynType
83
+ else
84
+ InstanceType.new(v)
85
+ end
86
+ end
87
+ end
88
+
89
+ rule(Number) do
90
+ v = ast.value
91
+ if v == Undef
92
+ RubyClass::Numeric
93
+ else
94
+ InstanceType.new(v)
95
+ end
96
+ end
97
+
98
+ rule(Reserved) do
99
+ case ast.name.to_sym
100
+ when :true
101
+ RubyClass::TrueClass
102
+ when :false
103
+ RubyClass::FalseClass
104
+ when :nil
105
+ RubyClass::NilClass
106
+ else
107
+ DynType
108
+ end
109
+ end
110
+
111
+ rule(Super) do
112
+ if type_env.context.nil?
113
+ DynType
114
+ else
115
+ RubyClass[type_env.context.superclass]
116
+ end
117
+ end
118
+
119
+ rule(SymbolLiteral) do
120
+ RubyClass::Symbol
121
+ end
122
+
123
+ rule(Unary) do
124
+ type(ast.expr)
125
+ end
126
+
127
+ rule(Binary) do
128
+ type(ast.right)
129
+ type(ast.left)
130
+ end
131
+
132
+ rule(Dots) do
133
+ RubyClass::Range
134
+ end
135
+
136
+ rule(StringInterpolation) do
137
+ RubyClass::String
138
+ end
139
+
140
+ rule(StringLiteral) do
141
+ RubyClass::String
142
+ end
143
+
144
+ rule(ConstPathRef) do
145
+ klass = ast.value
146
+ if klass.is_a?(Module)
147
+ RubyClass[klass]
148
+ else
149
+ DynType
150
+ end
151
+ end
152
+
153
+ rule(ArrayLiteral) do
154
+ RubyClass::Array
155
+ end
156
+
157
+ rule(Paren) do
158
+ type(ast.expression)
159
+ end
160
+
161
+ rule(HashLiteral) do
162
+ RubyClass::Hash
163
+ end
164
+
165
+ # Variable access or a method call without arguments.
166
+ #
167
+ rule(VariableCall) do
168
+ type = type_env.bound_name?(ast)
169
+ if type
170
+ type
171
+ else
172
+ get_call_expr_type(Call.make(name: ast.name, parent: ast.parent),
173
+ type_env, ast.name)
174
+ # This implementation invokes the method if the expression is
175
+ # a method call. It returns an {InstanceType} containing the
176
+ # resulting value.
177
+ #
178
+ #v = ast.do_invocation
179
+ #if v == Undef
180
+ # error_found!(ast, "no such variable or method: #{ast.name}")
181
+ #else
182
+ # InstanceType.new(v)
183
+ #end
184
+ end
185
+ end
186
+
187
+ # This rule can be overridden to delimit reification. This
188
+ # implementation in RubyTypeChecker does not delimit. It
189
+ # attempts to reify all methods invoked by the Call node.
190
+ # Another approach to delimit reification is to override
191
+ # get_return_type().
192
+ #
193
+ rule(Call) do
194
+ method_name = ast.name.to_sym
195
+ if method_name == :lambda
196
+ RubyClass::Proc
197
+ elsif method_name == :raise
198
+ RubyClass::Exception
199
+ else
200
+ get_call_expr_type(ast, type_env, method_name)
201
+ end
202
+ end
203
+
204
+ rule(ArrayRef) do
205
+ DynType
206
+ end
207
+
208
+ rule(Conditional) do
209
+ all_types = []
210
+ type(ast.cond)
211
+ all_types << type(ast.then)
212
+ ast.all_elsif.each do |cond_then|
213
+ type(cond_then[0])
214
+ all_types << type(cond_then[1])
215
+ end
216
+ all_types << type(ast.else)
217
+ UnionType.make(all_types)
218
+ end
219
+
220
+ rule(Loop) do
221
+ type(ast.cond)
222
+ type(ast.body)
223
+ DynType
224
+ end
225
+
226
+ rule(ForLoop) do
227
+ ast.vars.each {|v| bind_local_var(type_env, v, DynType) }
228
+ type(ast.body)
229
+ DynType
230
+ end
231
+
232
+ rule(Return) do
233
+ vs = ast.values
234
+ if vs.size == 1
235
+ type(vs[0])
236
+ elsif vs.size > 1
237
+ vs.map {|v| type(v) }
238
+ RubyClass::Array
239
+ else
240
+ Void
241
+ end
242
+ end
243
+
244
+ rule(Break) do
245
+ vs = ast.values
246
+ if vs.size == 1
247
+ type(vs[0])
248
+ elsif vs.size > 1
249
+ vs.map {|v| type(v) }
250
+ RubyClass::Array
251
+ else
252
+ Void
253
+ end
254
+ end
255
+
256
+ rule(Block) do
257
+ s = type_env.new_tenv
258
+ type_parameters(ast, s)
259
+ MethodType.new(ast, DynType, type(ast.body, s))
260
+ end
261
+
262
+ rule(Exprs) do
263
+ ast.expressions.reduce(DynType) {|t, e| type(e) }
264
+ end
265
+
266
+ rule(Rescue) do
267
+ s = type_env.new_tenv
268
+ ts = ast.types
269
+ unless ast.parameter.nil?
270
+ # unify all the exception types into one union type.
271
+ etype = if ts.empty?
272
+ DynType
273
+ else
274
+ rts = ts.map do |e|
275
+ clazz = e.value
276
+ type_assert(clazz.is_a?(Class), 'bad exception type')
277
+ RubyClass[clazz]
278
+ end
279
+ UnionType.make(rts)
280
+ end
281
+ bind_local_var(s, ast.parameter, etype)
282
+ end
283
+
284
+ all_types = []
285
+ all_types << type(ast.body, s)
286
+ all_types << type(ast.nested_rescue) unless ast.nested_rescue.nil?
287
+ all_types << type(ast.else) unless ast.else.nil?
288
+ type(ast.ensure)
289
+ UnionType.make(all_types)
290
+ end
291
+
292
+ rule(BeginEnd) do
293
+ body_t = type(ast.body)
294
+ if ast.rescue.nil?
295
+ body_t
296
+ else
297
+ UnionType.make(body_t, type(ast.rescue))
298
+ end
299
+ end
300
+
301
+ rule(Def) do
302
+ mtype = MethodType.new(ast, DynType, DynType)
303
+ type_assert_later do
304
+ s = type_env.new_tenv
305
+ type_parameters(ast, s) # ignore parameter types
306
+ body_t = type(ast.body, s)
307
+ res_t = if ast.rescue.nil?
308
+ body_t
309
+ else
310
+ UnionType.make(body_t, type(ast.rescue, s))
311
+ end
312
+ type_assert_subsume(mtype.result, res_t, 'bad result type')
313
+ end
314
+ mtype
315
+ end
316
+
317
+ rule(ModuleDef) do
318
+ s = type_env.new_tenv
319
+ type(ast.body, s)
320
+ type(ast.rescue, s) unless ast.rescue.nil?
321
+ end
322
+
323
+ rule(ClassDef) do
324
+ s = type_env.new_tenv
325
+ type(ast.body, s)
326
+ type(ast.rescue, s) unless ast.rescue.nil?
327
+ end
328
+
329
+ rule(SingularClassDef) do
330
+ s = type_env.new_tenv
331
+ type(ast.body, s)
332
+ type(ast.rescue, s) unless ast.rescue.nil?
333
+ end
334
+
335
+ # Helper methods for rules
336
+ # They can be overridden.
337
+
338
+ def bind_local_var(env, ast, var_type)
339
+ unless var_type.nil?
340
+ env.bind_name(ast, LocalVarType.new(var_type.copy(OptionalRole), ast))
341
+ @typetable[ast] = var_type
342
+ end
343
+ end
344
+
345
+ def type_assert_params(params, args, errmsg='')
346
+ unless params == DynType
347
+ type_assert(params.is_a?(Array), errmsg)
348
+ type_assert(args.is_a?(Array), errmsg)
349
+ type_assert(params.length <= args.length, errmsg) # ignore keyword params
350
+ params.each_with_index do |p, i|
351
+ type_assert_subsume(p, args[i], errmsg)
352
+ end
353
+ end
354
+ end
355
+
356
+ def type_assert_subsume(expected_type, actual_type, errmsg='')
357
+ type_assert(actual_type <= expected_type, errmsg)
358
+ end
359
+
360
+ def type_parameters(an_ast, tenv)
361
+ an_ast.params.each {|v| bind_local_var(tenv, v, DynType) }
362
+ an_ast.optionals.each {|v| bind_local_var(tenv, v[0], DynType) }
363
+ bind_local_var(tenv, an_ast.rest_of_params,
364
+ DynType) unless an_ast.rest_of_params.nil?
365
+ an_ast.keywords.each {|v| bind_local_var(tenv, v, DynType) }
366
+ bind_local_var(tenv, an_ast.rest_of_keywords,
367
+ DynType) unless an_ast.rest_of_keywords.nil?
368
+ bind_local_var(tenv, an_ast.block_param,
369
+ DynType) unless an_ast.block_param.nil?
370
+ end
371
+
372
+ # Computes the type of {Call} expression.
373
+ # If it finds `method_name` in `type_env`, it returns its type
374
+ # recorded in `type_env`.
375
+ #
376
+ # @param [Call] call_ast an AST.
377
+ # @param [TypeEnv] type_env a type environment.
378
+ # @param [String|Symbol] method_name a method name.
379
+ # @return [ResultType] the type of the resulting value.
380
+ def get_call_expr_type(call_ast, type_env, method_name)
381
+ arg_types = call_ast.args.map {|t| type(t) }
382
+ type(call_ast.block_arg)
383
+ type(call_ast.block)
384
+
385
+ if call_ast.receiver.nil?
386
+ found_t = type_env.bound_name?(method_name)
387
+ unless found_t.nil?
388
+ recv_type = DynType
389
+ else
390
+ recv_obj = call_ast.get_receiver_object
391
+ recv_type = if recv_obj.nil?
392
+ if type_env.context.nil?
393
+ DynType
394
+ else
395
+ RubyClass[type_env.context] # self's type
396
+ end
397
+ else
398
+ InstanceType.new(recv_obj)
399
+ end
400
+ end
401
+ else
402
+ found_t = nil
403
+ recv_type = type(call_ast.receiver)
404
+ end
405
+
406
+ if !found_t.nil?
407
+ found_t
408
+ elsif DynType == recv_type || DynType == recv_type.exact_type
409
+ DynType
410
+ else
411
+ lookup_builtin(recv_type, method_name) ||
412
+ lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
413
+ end
414
+ end
415
+
416
+ # @private
417
+ def lookup_builtin(recv_type, method_name)
418
+ et = recv_type.exact_type
419
+ if DynType == et
420
+ nil
421
+ else
422
+ mt = typedef(et)&.[](method_name)
423
+ if mt.nil?
424
+ nil
425
+ else
426
+ MethodType.role(mt)&.result
427
+ end
428
+ end
429
+ end
430
+
431
+ # @private
432
+ def lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
433
+ begin
434
+ mth = Type.get_instance_method_object(recv_type, method_name)
435
+ rescue CheckError => evar
436
+ error_found!(ast, evar.message)
437
+ end
438
+ new_tenv = type_env.new_base_tenv(recv_type.exact_type)
439
+ get_return_type(ast, mth, new_tenv, arg_types)
440
+ end
441
+
442
+ # Type-checks whether the argument types match parameter types.
443
+ # It returns a {ResultType}.
444
+ #
445
+ # Override this method to delimit reification. The implementation
446
+ # in this class reifies any method. The method does not have to
447
+ # return a ResultType, which can be used in a later phase to
448
+ # obtain the invoked method.
449
+ # This method is invoked by rule(Call). See rule(Call) for more details.
450
+ #
451
+ # @param [Call] an_ast the Call node.
452
+ # @param [Proc|Method|UnboundMethod] mthd the method invoked by an_ast.
453
+ # if `mthd` is nil, {get_return_type} reports an error.
454
+ # @param [TypeEnv] new_tenv a type environment.
455
+ # @param [Array<Type>] arg_types the types of the actual arguments.
456
+ # @return [ResultType] the result type.
457
+ def get_return_type(an_ast, mthd, new_tenv, arg_types)
458
+ m_ast = an_ast.root.reify(mthd)
459
+ type_assert_false(m_ast.nil?, "no source code: for #{mthd}")
460
+ (@syntax.check(m_ast.tree) || @syntax.raise_error) if @syntax
461
+
462
+ mtype = MethodType.role(type(m_ast.tree, new_tenv))
463
+ type_assert(mtype, 'not a method type')
464
+ type_assert_params(mtype.params, arg_types, 'argument type mismatch')
465
+ mtype.result
466
+ end
467
+ end # of RubyTypeChecker
468
+ end