houndstooth 0.1.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 +5 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/README.md +99 -0
- data/bin/houndstooth.rb +183 -0
- data/fuzz/cases/x.rb +8 -0
- data/fuzz/cases/y.rb +8 -0
- data/fuzz/cases/z.rb +22 -0
- data/fuzz/ruby.dict +64 -0
- data/fuzz/run +21 -0
- data/lib/houndstooth/environment/builder.rb +260 -0
- data/lib/houndstooth/environment/type_parser.rb +149 -0
- data/lib/houndstooth/environment/types/basic/type.rb +85 -0
- data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
- data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
- data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
- data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
- data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
- data/lib/houndstooth/environment/types/method/method.rb +79 -0
- data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
- data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
- data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
- data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
- data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
- data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
- data/lib/houndstooth/environment/types.rb +3 -0
- data/lib/houndstooth/environment.rb +74 -0
- data/lib/houndstooth/errors.rb +53 -0
- data/lib/houndstooth/instructions.rb +698 -0
- data/lib/houndstooth/interpreter/const_internal.rb +148 -0
- data/lib/houndstooth/interpreter/objects.rb +142 -0
- data/lib/houndstooth/interpreter/runtime.rb +309 -0
- data/lib/houndstooth/interpreter.rb +7 -0
- data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
- data/lib/houndstooth/semantic_node/definitions.rb +253 -0
- data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
- data/lib/houndstooth/semantic_node/keywords.rb +45 -0
- data/lib/houndstooth/semantic_node/literals.rb +226 -0
- data/lib/houndstooth/semantic_node/operators.rb +126 -0
- data/lib/houndstooth/semantic_node/parameters.rb +108 -0
- data/lib/houndstooth/semantic_node/send.rb +349 -0
- data/lib/houndstooth/semantic_node/super.rb +12 -0
- data/lib/houndstooth/semantic_node.rb +119 -0
- data/lib/houndstooth/stdlib.rb +6 -0
- data/lib/houndstooth/type_checker.rb +462 -0
- data/lib/houndstooth.rb +53 -0
- data/spec/ast_to_node_spec.rb +889 -0
- data/spec/environment_spec.rb +323 -0
- data/spec/instructions_spec.rb +291 -0
- data/spec/integration_spec.rb +785 -0
- data/spec/interpreter_spec.rb +170 -0
- data/spec/self_spec.rb +7 -0
- data/spec/spec_helper.rb +50 -0
- data/test/ruby_interpreter_test.rb +162 -0
- data/types/stdlib.htt +170 -0
- 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
|
data/lib/houndstooth.rb
ADDED
@@ -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
|
+
|