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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +108 -0
- data/Rakefile +10 -0
- data/lib/yadriggy.rb +32 -0
- data/lib/yadriggy/algebra.rb +497 -0
- data/lib/yadriggy/ast.rb +1839 -0
- data/lib/yadriggy/ast_location.rb +73 -0
- data/lib/yadriggy/ast_value.rb +428 -0
- data/lib/yadriggy/c.rb +11 -0
- data/lib/yadriggy/c/c.rb +220 -0
- data/lib/yadriggy/c/codegen.rb +481 -0
- data/lib/yadriggy/c/config.rb +51 -0
- data/lib/yadriggy/c/ctype.rb +118 -0
- data/lib/yadriggy/c/ctypecheck.rb +449 -0
- data/lib/yadriggy/c/ffi.rb +301 -0
- data/lib/yadriggy/c/opencl.rb +458 -0
- data/lib/yadriggy/c/program.rb +86 -0
- data/lib/yadriggy/c1.rb +10 -0
- data/lib/yadriggy/checker.rb +216 -0
- data/lib/yadriggy/eval.rb +200 -0
- data/lib/yadriggy/eval_all.rb +159 -0
- data/lib/yadriggy/pretty_print.rb +492 -0
- data/lib/yadriggy/printer.rb +82 -0
- data/lib/yadriggy/ruby_typecheck.rb +468 -0
- data/lib/yadriggy/ruby_typeinfer.rb +335 -0
- data/lib/yadriggy/source_code.rb +168 -0
- data/lib/yadriggy/syntax.rb +524 -0
- data/lib/yadriggy/type.rb +754 -0
- data/lib/yadriggy/typecheck.rb +277 -0
- data/lib/yadriggy/version.rb +5 -0
- data/yadriggy.gemspec +33 -0
- metadata +149 -0
@@ -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
|