houndstooth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +11 -0
  6. data/Gemfile.lock +49 -0
  7. data/README.md +99 -0
  8. data/bin/houndstooth.rb +183 -0
  9. data/fuzz/cases/x.rb +8 -0
  10. data/fuzz/cases/y.rb +8 -0
  11. data/fuzz/cases/z.rb +22 -0
  12. data/fuzz/ruby.dict +64 -0
  13. data/fuzz/run +21 -0
  14. data/lib/houndstooth/environment/builder.rb +260 -0
  15. data/lib/houndstooth/environment/type_parser.rb +149 -0
  16. data/lib/houndstooth/environment/types/basic/type.rb +85 -0
  17. data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
  18. data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
  19. data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
  20. data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
  21. data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
  22. data/lib/houndstooth/environment/types/method/method.rb +79 -0
  23. data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
  24. data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
  25. data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
  26. data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
  27. data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
  28. data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
  29. data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
  30. data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
  31. data/lib/houndstooth/environment/types.rb +3 -0
  32. data/lib/houndstooth/environment.rb +74 -0
  33. data/lib/houndstooth/errors.rb +53 -0
  34. data/lib/houndstooth/instructions.rb +698 -0
  35. data/lib/houndstooth/interpreter/const_internal.rb +148 -0
  36. data/lib/houndstooth/interpreter/objects.rb +142 -0
  37. data/lib/houndstooth/interpreter/runtime.rb +309 -0
  38. data/lib/houndstooth/interpreter.rb +7 -0
  39. data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
  40. data/lib/houndstooth/semantic_node/definitions.rb +253 -0
  41. data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
  42. data/lib/houndstooth/semantic_node/keywords.rb +45 -0
  43. data/lib/houndstooth/semantic_node/literals.rb +226 -0
  44. data/lib/houndstooth/semantic_node/operators.rb +126 -0
  45. data/lib/houndstooth/semantic_node/parameters.rb +108 -0
  46. data/lib/houndstooth/semantic_node/send.rb +349 -0
  47. data/lib/houndstooth/semantic_node/super.rb +12 -0
  48. data/lib/houndstooth/semantic_node.rb +119 -0
  49. data/lib/houndstooth/stdlib.rb +6 -0
  50. data/lib/houndstooth/type_checker.rb +462 -0
  51. data/lib/houndstooth.rb +53 -0
  52. data/spec/ast_to_node_spec.rb +889 -0
  53. data/spec/environment_spec.rb +323 -0
  54. data/spec/instructions_spec.rb +291 -0
  55. data/spec/integration_spec.rb +785 -0
  56. data/spec/interpreter_spec.rb +170 -0
  57. data/spec/self_spec.rb +7 -0
  58. data/spec/spec_helper.rb +50 -0
  59. data/test/ruby_interpreter_test.rb +162 -0
  60. data/types/stdlib.htt +170 -0
  61. metadata +110 -0
@@ -0,0 +1,462 @@
1
+ module Houndstooth
2
+ class TypeChecker
3
+ attr_reader :env
4
+
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ def process_block(block, lexical_context:, self_type:, const_context:, type_parameters:)
10
+ block.environment = env
11
+ block.instructions.each do |ins|
12
+ process_instruction(ins,
13
+ lexical_context: lexical_context,
14
+ self_type: self_type,
15
+ const_context: const_context,
16
+ type_parameters: type_parameters,
17
+ )
18
+ end
19
+ end
20
+
21
+ def process_instruction(ins, lexical_context:, self_type:, const_context:, type_parameters:)
22
+ case ins
23
+ when Instructions::LiteralInstruction
24
+ assign_type_to_literal_instruction(ins)
25
+
26
+ when Instructions::ConditionalInstruction
27
+ # Special case - did the conditional of this `if` statement come from an `is_a?`
28
+ # call immediately preceding it?
29
+ index = ins.block.instructions.index { _1 == ins }
30
+ if index != 0
31
+ last_ins = ins.block.instructions[index - 1]
32
+ if last_ins.is_a?(Instructions::SendInstruction) \
33
+ && last_ins.method_name == :is_a? \
34
+ && last_ins.result == ins.condition \
35
+ && last_ins.target.ruby_identifier \
36
+ && last_ins.arguments.length == 1
37
+
38
+ # Yep, we just checked the type of a local variable!
39
+ # In the true branch, we can refine the type
40
+ # What was the argument type? Uneigen it to get the type of the variable
41
+ arg_var = last_ins.arguments.first.variable
42
+ arg_type = ins.block.variable_type_at!(arg_var, ins)
43
+ checked_var_type = env.resolve_type(arg_type.type.uneigen).instantiate
44
+
45
+ ins.true_branch.type_refinements[last_ins.target] = checked_var_type
46
+ end
47
+ end
48
+
49
+ process_block(ins.true_branch, lexical_context: lexical_context, self_type: self_type, const_context: const_context, type_parameters: type_parameters)
50
+ process_block(ins.false_branch, lexical_context: lexical_context, self_type: self_type, const_context: const_context, type_parameters: type_parameters)
51
+
52
+ # A conditional could return either of its branches
53
+ ins.type_change = Environment::UnionType.new([
54
+ ins.true_branch.return_type!,
55
+ ins.false_branch.return_type!,
56
+ ]).simplify
57
+
58
+ when Instructions::AssignExistingInstruction
59
+ # If the assignment is to a different variable, set a typechange
60
+ if ins.result != ins.variable
61
+ ins.type_change = ins.block.variable_type_at!(ins.variable, ins)
62
+ end
63
+
64
+ when Instructions::SendInstruction
65
+ # Parse type arguments, if any
66
+ type_args = Environment::TypeParser.parse_type_arguments(env, ins.type_arguments, type_parameters)
67
+
68
+ # Get type of target
69
+ target_type = ins.block.variable_type_at!(ins.target, ins)
70
+
71
+ # Look up method on target
72
+ method = target_type.resolve_instance_method(ins.method_name, env)
73
+ if method.nil?
74
+ Houndstooth::Errors::Error.new(
75
+ "`#{target_type.rbs}` has no method named `#{ins.method_name}`",
76
+ [[ins.node.ast_node.loc.expression, "no such method"]]
77
+ ).push
78
+
79
+ # Assign result to untyped so type checking can continue
80
+ # TODO: create a special "abandoned" type specifically for this purpose
81
+ ins.type_change = Environment::UntypedType.new
82
+
83
+ return
84
+ end
85
+
86
+ # Get type of all arguments
87
+ arguments_with_types = ins.arguments.map do |arg|
88
+ [arg, ins.block.variable_type_at!(arg.variable, ins)]
89
+ end
90
+
91
+ # Resolve the best method signature with these
92
+ sig = method.resolve_matching_signature(target_type, arguments_with_types, type_args)
93
+ if sig.nil?
94
+ # Special case - if the node isn't a send, then it's a generated insertion to
95
+ # an array
96
+ if !ins.node.is_a?(Houndstooth::SemanticNode::Send)
97
+ # TODO: improve error
98
+ Houndstooth::Errors::Error.new(
99
+ "Incorrect type of element in array literal",
100
+ [[ins.node.ast_node.loc.expression, "type does not match annotation"]]
101
+ ).push
102
+ else
103
+ error_message =
104
+ "`#{target_type.rbs}` method `#{ins.method_name}` has no signature matching the given arguments\n"
105
+
106
+ if method.signatures.any?
107
+ error_message += "Available signatures are:\n" \
108
+ + method.signatures.map { |s| " - #{s.substitute_type_parameters(target_type, Hash.new).rbs}" }.join("\n")
109
+ else
110
+ error_message += "Method has no signatures - did you use a #: comment?"
111
+ end
112
+
113
+ Houndstooth::Errors::Error.new(
114
+ error_message,
115
+ # TODO: feels a bit dodgy
116
+ [[ins.node.ast_node.loc.expression, "no matching signature"]] \
117
+ + ins.node.arguments.zip(arguments_with_types).map do |node, (_, t)|
118
+ [node.node.ast_node.loc.expression, "argument type is `#{t.rbs}`"]
119
+ end
120
+ ).push
121
+ end
122
+
123
+ # Assign result to untyped so type checking can continue
124
+ # TODO: create a special "abandoned" type specifically for this purpose
125
+ ins.type_change = Environment::UntypedType.new
126
+
127
+ return
128
+ end
129
+
130
+ # Resolve type arguments - don't need to perform length check here, since the sig
131
+ # wouldn't have matched if the lengths mismatched
132
+ call_type_args = sig.type_parameters.zip(type_args).to_h
133
+ sig = sig.substitute_type_parameters(target_type, call_type_args)
134
+
135
+ # Handle block
136
+ catch :abort_block do
137
+ if sig.block_parameter && ins.method_block
138
+ # This method takes a block, and was given one
139
+ # Add parameter types
140
+ throw :abort_block \
141
+ if !add_parameter_type_instructions(ins, ins.method_block, sig.block_parameter.type)
142
+
143
+ # Recurse type checking into it
144
+ # We don't support overridden block self types, so we need to special-case
145
+ # this for `define_method`, where the block's `self` is uneigened
146
+ # `define_method` also strips constness
147
+ if method == env.resolve_type('::Module').resolve_instance_method(:define_method, env)
148
+ # HACK: Won't work with generics
149
+ inner_self_type = env.resolve_type(self_type.type.uneigen).instantiate
150
+ inner_const_context = false
151
+ else
152
+ inner_self_type = self_type
153
+ inner_const_context = const_context
154
+ end
155
+ process_block(ins.method_block, lexical_context: lexical_context, self_type: inner_self_type, const_context: inner_const_context, type_parameters: type_parameters)
156
+
157
+ # Check return type
158
+ if !sig.block_parameter.type.return_type.accepts?(ins.method_block.return_type!)
159
+ Houndstooth::Errors::Error.new(
160
+ "Incorrect return type for block, expected `#{sig.block_parameter.type.return_type.rbs}`",
161
+ [[
162
+ ins.method_block.instructions.last.node.ast_node.loc.expression,
163
+ "got `#{ins.method_block.return_type!.rbs}`"
164
+ ]]
165
+ ).push
166
+ throw :abort_block
167
+ end
168
+
169
+ elsif sig.block_parameter && !ins.method_block
170
+ # This method takes a block, but wasn't given one
171
+ # If the block is not optional, error
172
+ if !sig.block_parameter.optional?
173
+ Houndstooth::Errors::Error.new(
174
+ "`#{target_type.rbs}` method `#{ins.method_name}` requires a block, but none was given",
175
+ [[ins.node.ast_node.loc.expression, "expected block"]]
176
+ ).push
177
+ end
178
+ elsif !sig.block_parameter && ins.method_block
179
+ # This method doesn't take a block, but was given one
180
+ # That's not allowed!
181
+ # (Well, Ruby allows it, but it doesn't make sense for us - 99% of the time,
182
+ # this will be a bug)
183
+ if ins.method_block
184
+ Houndstooth::Errors::Error.new(
185
+ "`#{target_type.rbs}` method `#{ins.method_name}` does not accept a block",
186
+ [[ins.node.ast_node.loc.expression, "unexpected block"]]
187
+ ).push
188
+ end
189
+ end
190
+ end
191
+
192
+ # Check for return type special cases
193
+ case sig.return_type
194
+ when Environment::SelfType
195
+ ins.type_change = target_type
196
+ when Environment::InstanceType
197
+ ins.type_change = env.resolve_type(target_type.type.uneigen).instantiate(target_type.type_arguments)
198
+ else
199
+ # No special cases, set result variable to return type
200
+ ins.type_change = sig.return_type
201
+ end
202
+
203
+ # If we're in a const context, check that the target method is const
204
+ if const_context && !method.const?
205
+ Houndstooth::Errors::Error.new(
206
+ "`#{target_type.rbs}` method `#{ins.method_name}` is not const, so cannot be called from a const context",
207
+ [[ins.node.ast_node.loc.expression, "call inside a const context"]]
208
+ ).push
209
+ end
210
+
211
+ # If this method is const-required, check that the call was const-considered, or the
212
+ # method we're currently in is const-required
213
+ if method.const_required? && !ins.const_considered?
214
+ Houndstooth::Errors::Error.new(
215
+ "`#{target_type.rbs}` method `#{ins.method_name}` is const-required, but this call is not within a const-required context",
216
+ [[ins.node.ast_node.loc.expression, "call outside a const context"]]
217
+ ).push
218
+ end
219
+
220
+ when Instructions::ConstantBaseAccessInstruction
221
+ ins.type_change = Environment::BaseDefinedType.new
222
+
223
+ when Instructions::ConstantAccessInstruction
224
+ if ins.target
225
+ # TODO: will only work with types, not actual constants
226
+ target = ins.block.variable_type_at!(ins.target, ins)
227
+ else
228
+ target = lexical_context
229
+ end
230
+ resolved = env.resolve_type(ins.name.to_s, type_context: env.resolve_type(target.uneigen))
231
+
232
+ if resolved.nil?
233
+ Houndstooth::Errors::Error.new(
234
+ "No constant named `#{ins.name}` on `#{target.rbs}`",
235
+ [[ins.node.ast_node.loc.expression, "no such constant"]]
236
+ ).push
237
+
238
+ # Assign result to untyped so type checking can continue
239
+ # TODO: another use for "abandoned" type
240
+ ins.type_change = Environment::UntypedType.new
241
+ return
242
+ end
243
+
244
+ # Check type parameter numbers (even if the type doesn't have any - we don't want
245
+ # to allow type arguments when none are expected)
246
+ el = resolved.type_parameters.length
247
+ gl = ins.type_arguments.length
248
+ if el != gl
249
+ Houndstooth::Errors::Error.new(
250
+ "Insufficient type arguments for `#{ins.name}` (expected #{el}, got #{gl})",
251
+ [[ins.node.ast_node.loc.expression, "incorrect number of arguments"]]
252
+ ).push
253
+
254
+ # Assign result to untyped so type checking can continue
255
+ # TODO: another use for "abandoned" type
256
+ ins.type_change = Environment::UntypedType.new
257
+ return
258
+ end
259
+
260
+ # Does the type require type parameters?
261
+ if resolved.type_parameters.any?
262
+ # Yep - parse them
263
+ type_args = Environment::TypeParser.parse_type_arguments(env, ins.type_arguments, type_parameters)
264
+ else
265
+ type_args = []
266
+ end
267
+
268
+ ins.type_change = resolved.eigen.instantiate(type_args)
269
+
270
+ when Instructions::SelfInstruction
271
+ ins.type_change = self_type
272
+
273
+ when Instructions::TypeDefinitionInstruction
274
+ if ins.target
275
+ base_type = ins.block.variable_type_at!(ins.target, ins)
276
+ else
277
+ base_type = lexical_context
278
+ end
279
+
280
+ type_being_defined = env.resolve_type("#{base_type.uneigen}::#{ins.name}").eigen
281
+ type_being_defined_inst = type_being_defined.instantiate
282
+
283
+ process_block(
284
+ ins.body,
285
+ lexical_context: type_being_defined,
286
+ self_type: type_being_defined_inst,
287
+ # Type definitions bodies are always const
288
+ const_context: true,
289
+ type_parameters: type_parameters + type_being_defined.type_parameters,
290
+ )
291
+
292
+ # Returns the just-defined type
293
+ ins.type_change = type_being_defined_inst
294
+
295
+ when Instructions::MethodDefinitionInstruction
296
+ method, inner_self_type = ins.resolve_method_and_type(self_type, env)
297
+
298
+ # If this method is const-required, mark its body as const-considered
299
+ if method.const_required?
300
+ ins.body.mark_const_considered
301
+ end
302
+
303
+ # Does it have any signatures?
304
+ if method.signatures.empty?
305
+ Houndstooth::Errors::Error.new(
306
+ "No signatures provided",
307
+ [[ins.node.ast_node.loc.expression, "no signatures"]]
308
+ ).push
309
+ end
310
+
311
+ # Check each signature
312
+ method.signatures.map do |sig|
313
+ # Assign parameter types
314
+ number_of_type_ins = add_parameter_type_instructions(ins, ins.body, sig)
315
+ if number_of_type_ins == false
316
+ next
317
+ end
318
+
319
+ # Recurse into body
320
+ process_block(
321
+ ins.body,
322
+ self_type: inner_self_type,
323
+ lexical_context: lexical_context,
324
+ # Method definition bodies are const iff the method being defined is
325
+ const_context: method.const?,
326
+ type_parameters: type_parameters + sig.type_parameters,
327
+ )
328
+
329
+ # Check return type
330
+ if !sig.return_type.accepts?(ins.body.return_type!)
331
+ Houndstooth::Errors::Error.new(
332
+ "Incorrect return type for method, expected `#{sig.return_type.rbs}`",
333
+ [[
334
+ ins.body.instructions.last.node.ast_node.loc.expression,
335
+ "got `#{ins.body.return_type!.rbs}`"
336
+ ]]
337
+ ).push
338
+ end
339
+
340
+ # If there's more than one, remove type instructions so the next loop can add
341
+ # them again
342
+ # (We could remove them if there's only one too, but it's handy to leave in for
343
+ # debugging)
344
+ if method.signatures.length > 1
345
+ number_of_type_ins.times { ins.body.instructions.shift }
346
+ end
347
+ end
348
+
349
+ # Returns a symbol of the method's name
350
+ ins.type_change = env.resolve_type("Symbol").instantiate
351
+
352
+ when Instructions::ToStringInstruction
353
+ ins.type_change = env.resolve_type("String").instantiate
354
+
355
+ when Instructions::InstanceVariableReadInstruction
356
+ var_type = self_type.type.resolve_instance_variable(ins.name)
357
+ if var_type.nil?
358
+ Houndstooth::Errors::Error.new(
359
+ "Instance variable #{ins.name} is not defined",
360
+ [[ins.node.ast_node.loc.expression, "undefined"]]
361
+ ).push
362
+ ins.type_change = Environment::UntypedType.new
363
+ return
364
+ end
365
+
366
+ ins.type_change = var_type
367
+
368
+ when Instructions::InstanceVariableWriteInstruction
369
+ var_type = self_type.type.resolve_instance_variable(ins.name)
370
+ if var_type.nil?
371
+ Houndstooth::Errors::Error.new(
372
+ "Instance variable #{ins.name} is not defined",
373
+ [[ins.node.ast_node.loc.expression, "undefined"]]
374
+ ).push
375
+ ins.type_change = Environment::UntypedType.new
376
+ return
377
+ end
378
+
379
+ value_type = ins.block.variable_type_at!(ins.value, ins)
380
+
381
+ if !var_type.accepts?(value_type)
382
+ Houndstooth::Errors::Error.new(
383
+ "Cannot assign `#{value_type.rbs}` to #{ins.name}",
384
+ [[ins.node.ast_node.loc.expression, "Expected `#{var_type.rbs}`"]]
385
+ ).push
386
+ end
387
+
388
+ ins.type_change = var_type
389
+
390
+ else
391
+ raise "internal error: don\'t know how to type check #{ins.class.to_s}"
392
+ end
393
+ end
394
+
395
+ # Given a `LiteralInstruction`, assigns a result type based on the value. Assumes that the
396
+ # stdlib is loaded into the environment.
397
+ # @param [LiteralInstruction] ins
398
+ def assign_type_to_literal_instruction(ins)
399
+ ins.type_change =
400
+ case ins.value
401
+ when Integer
402
+ env.resolve_type("Integer").instantiate
403
+ when Float
404
+ env.resolve_type("Float").instantiate
405
+ when String
406
+ env.resolve_type("String").instantiate
407
+ when Symbol
408
+ env.resolve_type("Symbol").instantiate
409
+ when TrueClass
410
+ env.resolve_type("TrueClass").instantiate
411
+ when FalseClass
412
+ env.resolve_type("FalseClass").instantiate
413
+ when NilClass
414
+ env.resolve_type("NilClass").instantiate
415
+ else
416
+ Houndstooth::Errors::Error.new(
417
+ "Internal bug - encountered a literal with an unknown type",
418
+ [[ins.node.ast_node.loc.expression, "literal"]]
419
+ ).push
420
+ end
421
+ end
422
+
423
+ # Inserts instructions into the beginning of block to assign types to a set of parameter
424
+ # variables.
425
+ #
426
+ # @param [Instruction] ins The instruction this is relevant to. Only used for error
427
+ # reporting and for the nodes of the new instructions.
428
+ # @param [InstructionBlock] block The instruction block to prepend the new instructions to.
429
+ # @param [MethodType] method_type The method type to retrieve parameter types from.
430
+ # @return [Integer, false] False if an error occurred, otherwise the number of instructions
431
+ # added to the beginning.
432
+ def add_parameter_type_instructions(ins, block, method_type)
433
+ # Check parameter count
434
+ # TODO: this won't work if we support other kinds of parameter
435
+ expected_ps = method_type.positional_parameters
436
+ got_ps = block.parameters
437
+ expected_l = expected_ps.length
438
+ got_l = got_ps.length
439
+ if expected_l != got_l
440
+ Houndstooth::Errors::Error.new(
441
+ "Incorrect number of parameters (expected #{expected_l}, got #{got_l})",
442
+ [[ins.node.ast_node.loc.expression, "incorrect parameters"]]
443
+ ).push
444
+ return false
445
+ end
446
+
447
+ # Insert an instruction to assign each parameter's type
448
+ expected_ps.zip(got_ps).each do |type_param, var|
449
+ i = Instructions::AssignExistingInstruction.new(
450
+ block: block,
451
+ node: ins.node,
452
+ result: var,
453
+ variable: var,
454
+ )
455
+ i.type_change = type_param.type
456
+ block.instructions.unshift(i)
457
+ end
458
+
459
+ expected_ps.length
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,53 @@
1
+ module Houndstooth; end
2
+
3
+ require_relative 'houndstooth/errors'
4
+ require_relative 'houndstooth/instructions'
5
+ require_relative 'houndstooth/semantic_node'
6
+ require_relative 'houndstooth/environment'
7
+ require_relative 'houndstooth/stdlib'
8
+ require_relative 'houndstooth/type_checker'
9
+ require_relative 'houndstooth/interpreter'
10
+
11
+ module Houndstooth
12
+ # Parses a complete file, and adds its type definitions to the given environment.
13
+ # Returns the parsed `SemanticNode`, or if a syntax error occurs, returns `nil`.
14
+ def self.process_file(file_name, file_contents, env)
15
+ # Build parser buffer
16
+ begin
17
+ buffer = Parser::Source::Buffer.new(file_name)
18
+ buffer.source = file_contents
19
+ rescue => e
20
+ Houndstooth::Errors::Error.new("Error building parse buffer: #{e}", []).push
21
+ abort_on_error!
22
+ end
23
+
24
+ # Parse file into AST nodes
25
+ any_errors = false
26
+ parser = Parser::Ruby30.new
27
+
28
+ parser.diagnostics.consumer = ->(diag) do
29
+ any_errors = true
30
+ Houndstooth::Errors::Error.new(
31
+ "Syntax error",
32
+ [[diag.location, diag.message]]
33
+ ).push
34
+ end
35
+ begin
36
+ ast_node, comments = parser.parse_with_comments(buffer)
37
+ rescue Parser::SyntaxError => e
38
+ # We already got a diagnostic for this, don't need to handle it again
39
+ end
40
+ $comments = comments
41
+
42
+ return nil if any_errors
43
+
44
+ # Convert to semantic nodes
45
+ node = Houndstooth::SemanticNode.from_ast(ast_node)
46
+
47
+ # Build environment items
48
+ Houndstooth::Environment::Builder.new(node, env).analyze
49
+
50
+ node
51
+ end
52
+ end
53
+