yadriggy 1.0.0

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.
@@ -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