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,785 @@
|
|
1
|
+
RSpec.describe 'integration tests' do
|
2
|
+
def check_type_of(code, variable=nil, expect_success: true, &blk)
|
3
|
+
$cli_options = {}
|
4
|
+
|
5
|
+
# Prepare environment
|
6
|
+
env = Houndstooth::Environment.new
|
7
|
+
Houndstooth.process_file('stdlib.htt', File.read(File.join(__dir__, '..', 'types', 'stdlib.htt')), env)
|
8
|
+
node = Houndstooth.process_file('test code', code, env)
|
9
|
+
env.resolve_all_pending_types
|
10
|
+
|
11
|
+
# Create instruction block
|
12
|
+
block = Houndstooth::Instructions::InstructionBlock.new(has_scope: true, parent: nil)
|
13
|
+
node.to_instructions(block)
|
14
|
+
env.types["__HoundstoothMain"] = Houndstooth::Environment::DefinedType.new(path: "__HoundstoothMain")
|
15
|
+
|
16
|
+
# Run the interpreter
|
17
|
+
runtime = Houndstooth::Interpreter::Runtime.new(env: env)
|
18
|
+
runtime.execute_from_top_level(block)
|
19
|
+
|
20
|
+
# Skip type checking if any errors occured
|
21
|
+
unless Houndstooth::Errors.errors.any?
|
22
|
+
# Run type checker
|
23
|
+
checker = Houndstooth::TypeChecker.new(env)
|
24
|
+
checker.process_block(
|
25
|
+
block,
|
26
|
+
lexical_context: Houndstooth::Environment::BaseDefinedType.new,
|
27
|
+
self_type: env.types["__HoundstoothMain"],
|
28
|
+
const_context: false,
|
29
|
+
type_parameters: [],
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
# If we're expecting success, throw exception if there was an error
|
34
|
+
raise 'unexpected type errors' if expect_success && Houndstooth::Errors.errors.any?
|
35
|
+
|
36
|
+
# If we're not expecting success, check an error occured
|
37
|
+
if !expect_success
|
38
|
+
if Houndstooth::Errors.errors.any?
|
39
|
+
# Clear errors so test does not fail later
|
40
|
+
Houndstooth::Errors.reset
|
41
|
+
return
|
42
|
+
else
|
43
|
+
raise 'no error occurred but expected one'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if variable
|
48
|
+
# Very aggressively look for the variable and grab its type
|
49
|
+
# (It might only exist in an inner scope)
|
50
|
+
var = nil
|
51
|
+
block.walk do |n|
|
52
|
+
if n.is_a?(Houndstooth::Instructions::InstructionBlock)
|
53
|
+
var = n.resolve_local_variable(variable, create: false) rescue nil
|
54
|
+
break unless var.nil?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
raise "couldn't find variable #{variable}" if var.nil?
|
59
|
+
type = block.variable_type_at!(var, block.instructions.last)
|
60
|
+
|
61
|
+
raise "type check for #{code} failed" if !env.instance_exec(type, &blk)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'assigns types literals' do
|
66
|
+
check_type_of('x = 3', 'x') { |t| t.type == resolve_type('Integer') }
|
67
|
+
check_type_of('x = "hello"', 'x') { |t| t.type == resolve_type('String') }
|
68
|
+
check_type_of('x = 3.2', 'x') { |t| t.type == resolve_type('Float') }
|
69
|
+
check_type_of('x = true', 'x') { |t| t.type == resolve_type('TrueClass') }
|
70
|
+
check_type_of('x = nil', 'x') { |t| t.type == resolve_type('NilClass') }
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'resolves methods and selects appropriate overloads' do
|
74
|
+
# Basic resolution
|
75
|
+
check_type_of('x = (-3).abs', 'x') { |t| t.type == resolve_type('Integer') }
|
76
|
+
|
77
|
+
# Overload selection
|
78
|
+
check_type_of('x = 3 + 3', 'x') { |t| t.type == resolve_type('Integer') }
|
79
|
+
check_type_of('x = 3 + 3.2', 'x') { |t| t.type == resolve_type('Float') }
|
80
|
+
check_type_of('x = 3.2 + 3', 'x') { |t| t.type == resolve_type('Float') }
|
81
|
+
|
82
|
+
# Errors
|
83
|
+
check_type_of('x = 3.non_existent_method', expect_success: false) # Method doesn't exist
|
84
|
+
check_type_of('x = 3.+()', expect_success: false) # Too few arguments
|
85
|
+
check_type_of('x = 3.+(1, 2, 3)', expect_success: false) # Too many arguments
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'checks blocks passed to methods' do
|
89
|
+
# Correct usage
|
90
|
+
# (procarg not supported yet, hence |x,|)
|
91
|
+
check_type_of('3.times { |x,| Kernel.puts 1 + x }')
|
92
|
+
|
93
|
+
# Incorrect usages
|
94
|
+
check_type_of('3.times { || Kernel.puts x }', expect_success: false) # Too few params
|
95
|
+
check_type_of('3.times { |x, y| Kernel.puts x }', expect_success: false) # Too many params
|
96
|
+
check_type_of('3.times { |x| Kernel.puts x }', expect_success: false) # Unsupported param
|
97
|
+
check_type_of('3.times', expect_success: false) # Missing block
|
98
|
+
check_type_of('1.+(1) { |x| "what" }', expect_success: false) # Block where not taken
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'creates unions from flow-sensitivity' do
|
102
|
+
# True branch assigns
|
103
|
+
check_type_of('
|
104
|
+
x = 3
|
105
|
+
if Kernel.rand
|
106
|
+
x = "hello"
|
107
|
+
else
|
108
|
+
y = 2
|
109
|
+
end
|
110
|
+
', 'x') do |t|
|
111
|
+
t.is_a?(E::UnionType) \
|
112
|
+
&& t.types.find { |t| t.type == resolve_type("Integer") } \
|
113
|
+
&& t.types.find { |t| t.type == resolve_type("String") }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Both branches assign to different types
|
117
|
+
check_type_of('
|
118
|
+
x = 3
|
119
|
+
if Kernel.rand
|
120
|
+
x = "hello"
|
121
|
+
else
|
122
|
+
x = 3.2
|
123
|
+
end
|
124
|
+
', 'x') do |t|
|
125
|
+
t.is_a?(E::UnionType) \
|
126
|
+
&& t.types.find { |t| t.type == resolve_type("Float") } \
|
127
|
+
&& t.types.find { |t| t.type == resolve_type("String") }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Both branches assign to the same type
|
131
|
+
check_type_of('
|
132
|
+
x = 3
|
133
|
+
if Kernel.rand
|
134
|
+
x = "hello"
|
135
|
+
else
|
136
|
+
x = "goodbye"
|
137
|
+
end
|
138
|
+
', 'x') { |t| t.type == resolve_type("String") }
|
139
|
+
|
140
|
+
# Blocks *could* execute, changing the type of a variable
|
141
|
+
check_type_of('
|
142
|
+
x = 3
|
143
|
+
#!arg String
|
144
|
+
["x", "y", "z"].each do |y,|
|
145
|
+
x = y
|
146
|
+
end
|
147
|
+
', 'x') do |t|
|
148
|
+
t.is_a?(E::UnionType) \
|
149
|
+
&& t.types.find { |t| t.type == resolve_type("Integer") } \
|
150
|
+
&& t.types.find { |t| t.type == resolve_type("String") }
|
151
|
+
end
|
152
|
+
|
153
|
+
# Blocks which don't affect the variable's type won't change it
|
154
|
+
check_type_of('
|
155
|
+
x = 3
|
156
|
+
#!arg String
|
157
|
+
["x", "y", "z"].each do |y,|
|
158
|
+
Kernel.puts y
|
159
|
+
end
|
160
|
+
', 'x') { |t| t.type == resolve_type("Integer") }
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'checks module definitions' do
|
164
|
+
# Module definition
|
165
|
+
check_type_of('
|
166
|
+
module A
|
167
|
+
#: () -> String
|
168
|
+
def self.foo
|
169
|
+
"Hello"
|
170
|
+
end
|
171
|
+
|
172
|
+
#: () -> String
|
173
|
+
def self.bar
|
174
|
+
"there"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
x = A.foo + " " + A.bar
|
179
|
+
', 'x') { |t| t.type == resolve_type("String") }
|
180
|
+
|
181
|
+
# Module methods are available on defined modules
|
182
|
+
check_type_of('
|
183
|
+
module A
|
184
|
+
#: () -> String
|
185
|
+
def self.foo
|
186
|
+
"Hello"
|
187
|
+
end
|
188
|
+
|
189
|
+
#: () -> String
|
190
|
+
def self.bar
|
191
|
+
"there"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
x = A.nesting
|
196
|
+
') # TODO: make more precise once arrays exist
|
197
|
+
|
198
|
+
# Methods defined on one module don't exist on another
|
199
|
+
# (i.e. eigens are probably isolated)
|
200
|
+
check_type_of('
|
201
|
+
module A
|
202
|
+
#: () -> String
|
203
|
+
def self.foo
|
204
|
+
"Hello"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
module B
|
209
|
+
#: () -> String
|
210
|
+
def self.bar
|
211
|
+
"there"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
x = B.foo
|
216
|
+
', expect_success: false)
|
217
|
+
|
218
|
+
# Non-self methods can't be called on modules directly
|
219
|
+
check_type_of('
|
220
|
+
module A
|
221
|
+
#: () -> String
|
222
|
+
def foo
|
223
|
+
"hello"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
A.foo
|
228
|
+
', expect_success: false)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'checks class definitions' do
|
232
|
+
# Class definition, with both instance and static methods
|
233
|
+
check_type_of('
|
234
|
+
class A
|
235
|
+
#: () -> String
|
236
|
+
def foo
|
237
|
+
"Hello"
|
238
|
+
end
|
239
|
+
|
240
|
+
#: () -> String
|
241
|
+
def self.bar
|
242
|
+
"there"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
x = A.new.foo + " " + A.bar
|
247
|
+
', 'x') { |t| t.type == resolve_type("String") }
|
248
|
+
|
249
|
+
# Subclassing
|
250
|
+
check_type_of('
|
251
|
+
class A
|
252
|
+
#: () -> String
|
253
|
+
def foo
|
254
|
+
"Hello"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class B < A
|
259
|
+
#: () -> String
|
260
|
+
def bar
|
261
|
+
"there"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
b = B.new
|
266
|
+
x = b.foo + " " + b.bar
|
267
|
+
', 'x') { |t| t.type == resolve_type("String") }
|
268
|
+
|
269
|
+
# Pulls constructor parameters into `new`
|
270
|
+
check_type_of('
|
271
|
+
class Person
|
272
|
+
#: (String, Integer) -> void
|
273
|
+
def initialize(name, age)
|
274
|
+
Kernel.puts "Created #{name}, who is #{age}"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
x = Person.new("Aaron", 21)
|
279
|
+
', 'x') { |t| t.type == resolve_type("Person") }
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'checks method definitions' do
|
283
|
+
# Basic checking
|
284
|
+
check_type_of('
|
285
|
+
class A
|
286
|
+
#: (Integer, Integer) -> Integer
|
287
|
+
def add(a, b)
|
288
|
+
a + b
|
289
|
+
end
|
290
|
+
end
|
291
|
+
')
|
292
|
+
check_type_of('
|
293
|
+
class A
|
294
|
+
#: (String) -> Integer
|
295
|
+
def foo(x)
|
296
|
+
foo.abs
|
297
|
+
end
|
298
|
+
end
|
299
|
+
', expect_success: false)
|
300
|
+
|
301
|
+
# Parameter count mismatch
|
302
|
+
check_type_of('
|
303
|
+
class A
|
304
|
+
#: (Integer) -> Integer
|
305
|
+
def add(a, b)
|
306
|
+
a + b
|
307
|
+
end
|
308
|
+
end
|
309
|
+
', expect_success: false)
|
310
|
+
check_type_of('
|
311
|
+
class A
|
312
|
+
#: (Integer, Integer) -> Integer
|
313
|
+
def foo(a)
|
314
|
+
a
|
315
|
+
end
|
316
|
+
end
|
317
|
+
', expect_success: false)
|
318
|
+
|
319
|
+
# Must have a signature
|
320
|
+
check_type_of('
|
321
|
+
class A
|
322
|
+
def foo(a)
|
323
|
+
a
|
324
|
+
end
|
325
|
+
end
|
326
|
+
', expect_success: false)
|
327
|
+
|
328
|
+
# If the definition has multiple signatures, they're all checked
|
329
|
+
check_type_of('
|
330
|
+
class A
|
331
|
+
#: (Float, Float) -> Float
|
332
|
+
#: (Integer, Integer) -> Integer
|
333
|
+
#: (String, String) -> String
|
334
|
+
def add(a, b)
|
335
|
+
a + b
|
336
|
+
end
|
337
|
+
end
|
338
|
+
')
|
339
|
+
check_type_of('
|
340
|
+
class A
|
341
|
+
#: (Float, Float) -> Float
|
342
|
+
#: (Integer, Integer) -> Integer
|
343
|
+
#: (String, String) -> String
|
344
|
+
#: (Object, Object) -> Object
|
345
|
+
def add(a, b)
|
346
|
+
a + b
|
347
|
+
end
|
348
|
+
end
|
349
|
+
', expect_success: false)
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'checks plain code inside type definitions' do
|
353
|
+
# Checks actual snippets of code in definitions
|
354
|
+
check_type_of('
|
355
|
+
class X
|
356
|
+
Kernel.puts 2 + 2
|
357
|
+
end
|
358
|
+
')
|
359
|
+
check_type_of('
|
360
|
+
class X
|
361
|
+
Kernel.puts 2 + "hello"
|
362
|
+
end
|
363
|
+
', expect_success: false)
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'allows usage of type parameters' do
|
367
|
+
check_type_of('
|
368
|
+
#!param T
|
369
|
+
class X
|
370
|
+
#: (T) -> T
|
371
|
+
def identity(obj)
|
372
|
+
obj
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#!arg String
|
377
|
+
x = X.new
|
378
|
+
y = x.identity("Hello")
|
379
|
+
', 'y') { |t| t.type == resolve_type("String") }
|
380
|
+
|
381
|
+
check_type_of('
|
382
|
+
#!arg String
|
383
|
+
x = Array.new
|
384
|
+
', 'x') do |t|
|
385
|
+
t.type == resolve_type("Array") \
|
386
|
+
&& t.type_arguments.map(&:type) == [resolve_type("String")]
|
387
|
+
end
|
388
|
+
|
389
|
+
check_type_of('
|
390
|
+
#!arg String
|
391
|
+
x = Array.new
|
392
|
+
y = (x << "foo")
|
393
|
+
', 'y') do |t|
|
394
|
+
t.type == resolve_type("Array") \
|
395
|
+
&& t.type_arguments.map(&:type) == [resolve_type("String")]
|
396
|
+
end
|
397
|
+
|
398
|
+
check_type_of('
|
399
|
+
#!arg String
|
400
|
+
x = Array.new
|
401
|
+
x << "foo"
|
402
|
+
x << "bar"
|
403
|
+
y = x[0]
|
404
|
+
', 'y') { |t| t.type == resolve_type("String") }
|
405
|
+
|
406
|
+
check_type_of('
|
407
|
+
#!arg String
|
408
|
+
x = ["foo", "bar", "baz"]
|
409
|
+
', 'x') do |t|
|
410
|
+
t.type == resolve_type("Array") \
|
411
|
+
&& t.type_arguments.map(&:type) == [resolve_type("String")]
|
412
|
+
end
|
413
|
+
|
414
|
+
check_type_of('
|
415
|
+
#!arg String
|
416
|
+
x = ["foo", 3, "baz"]
|
417
|
+
', expect_success: false)
|
418
|
+
end
|
419
|
+
|
420
|
+
it 'allows usage of instance variables' do
|
421
|
+
check_type_of('
|
422
|
+
#!var @name String
|
423
|
+
class Person
|
424
|
+
#: () -> String
|
425
|
+
def name
|
426
|
+
@name
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
x = Person.new.name
|
431
|
+
', 'x') { |t| t.type == resolve_type('String') }
|
432
|
+
|
433
|
+
check_type_of('
|
434
|
+
#!var @name String
|
435
|
+
class Person
|
436
|
+
#: (String) -> void
|
437
|
+
def name=(n)
|
438
|
+
@name = n
|
439
|
+
end
|
440
|
+
|
441
|
+
#: () -> String
|
442
|
+
def name
|
443
|
+
@name
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
x = Person.new
|
448
|
+
x.name = "Aaron"
|
449
|
+
y = x.name
|
450
|
+
', 'y') { |t| t.type == resolve_type('String') }
|
451
|
+
|
452
|
+
check_type_of('
|
453
|
+
#!var @name String
|
454
|
+
class Person
|
455
|
+
#: () -> Integer
|
456
|
+
def name
|
457
|
+
@name
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
x = Person.new
|
462
|
+
x.name = "Aaron"
|
463
|
+
y = x.name
|
464
|
+
', expect_success: false)
|
465
|
+
|
466
|
+
check_type_of('
|
467
|
+
#!var @name String
|
468
|
+
class Person
|
469
|
+
#: (Object) -> void
|
470
|
+
def name=(n)
|
471
|
+
@name = n
|
472
|
+
end
|
473
|
+
|
474
|
+
#: () -> String
|
475
|
+
def name
|
476
|
+
@name
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
x = Person.new
|
481
|
+
x.name = "Aaron"
|
482
|
+
y = x.name
|
483
|
+
', expect_success: false)
|
484
|
+
end
|
485
|
+
|
486
|
+
it 'recognises is_a? to refine types back' do
|
487
|
+
check_type_of('
|
488
|
+
if Kernel.rand > 0.5
|
489
|
+
x = 3
|
490
|
+
else
|
491
|
+
x = "hello"
|
492
|
+
end
|
493
|
+
|
494
|
+
if x.is_a?(Integer)
|
495
|
+
y = x.abs
|
496
|
+
end
|
497
|
+
', 'y') do |t|
|
498
|
+
t.is_a?(E::UnionType) \
|
499
|
+
&& t.types.find { |t| t.type == resolve_type("Integer") } \
|
500
|
+
&& t.types.find { |t| t.type == resolve_type("NilClass") }
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'checks that const-required calls are used in const contexts' do
|
505
|
+
# Call to const-required-internal from type definition body
|
506
|
+
check_type_of('
|
507
|
+
class X
|
508
|
+
#: () -> String
|
509
|
+
def foo
|
510
|
+
"foo"
|
511
|
+
end
|
512
|
+
|
513
|
+
private :foo
|
514
|
+
end
|
515
|
+
')
|
516
|
+
|
517
|
+
# Call from non-const context
|
518
|
+
check_type_of('
|
519
|
+
class X
|
520
|
+
#: () -> String
|
521
|
+
def foo
|
522
|
+
"foo"
|
523
|
+
end
|
524
|
+
|
525
|
+
if Kernel.rand > 0.5
|
526
|
+
private :foo
|
527
|
+
end
|
528
|
+
end
|
529
|
+
', expect_success: false)
|
530
|
+
|
531
|
+
# Call to const-required-internal from const-required, and then call to that const-required
|
532
|
+
# from type definition body
|
533
|
+
check_type_of('
|
534
|
+
class X
|
535
|
+
#: (Symbol, Symbol) -> void
|
536
|
+
#!const required
|
537
|
+
def self.private_two(x, y)
|
538
|
+
private x
|
539
|
+
private y
|
540
|
+
end
|
541
|
+
|
542
|
+
#: () -> void
|
543
|
+
def foo; end
|
544
|
+
|
545
|
+
#: () -> void
|
546
|
+
def bar; end
|
547
|
+
|
548
|
+
private_two :foo, :bar
|
549
|
+
end
|
550
|
+
')
|
551
|
+
end
|
552
|
+
|
553
|
+
it 'checks that const methods call other only other const methods' do
|
554
|
+
# Valid - calls only const-internal methods
|
555
|
+
check_type_of('
|
556
|
+
class X
|
557
|
+
#: (Integer, Integer) -> Integer
|
558
|
+
#!const
|
559
|
+
def add_two(x, y)
|
560
|
+
x + y
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
x = X.new.add_two(1, 2)
|
565
|
+
', 'x') { |t| t.type == resolve_type('::Integer') }
|
566
|
+
|
567
|
+
# Valid - calls another const method
|
568
|
+
check_type_of('
|
569
|
+
class X
|
570
|
+
#: (Integer, Integer, Integer) -> Integer
|
571
|
+
#!const
|
572
|
+
def add_three(x, y, z)
|
573
|
+
add_two(add_two(x, y), z)
|
574
|
+
end
|
575
|
+
|
576
|
+
#: (Integer, Integer) -> Integer
|
577
|
+
#!const
|
578
|
+
def add_two(x, y)
|
579
|
+
x + y
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
x = X.new.add_three(1, 2, 3)
|
584
|
+
', 'x') { |t| t.type == resolve_type('::Integer') }
|
585
|
+
|
586
|
+
# Invalid - calls non-const from const
|
587
|
+
check_type_of('
|
588
|
+
class X
|
589
|
+
#: () -> Float
|
590
|
+
#!const
|
591
|
+
def fake_const_float
|
592
|
+
Kernel.rand
|
593
|
+
end
|
594
|
+
end
|
595
|
+
', expect_success: false)
|
596
|
+
end
|
597
|
+
|
598
|
+
it 'allows type parameters on methods' do
|
599
|
+
# Normal call
|
600
|
+
check_type_of('
|
601
|
+
module X
|
602
|
+
#: [A] (A) -> A
|
603
|
+
def self.identity(x)
|
604
|
+
x
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
x = X
|
609
|
+
#!arg Integer
|
610
|
+
.identity(3)
|
611
|
+
', 'x') { |t| t.type == resolve_type('Integer') }
|
612
|
+
|
613
|
+
# Insufficient type arguments
|
614
|
+
check_type_of('
|
615
|
+
module X
|
616
|
+
#: [A] (A) -> A
|
617
|
+
def self.identity(x)
|
618
|
+
x
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
x = X.identity(3)
|
623
|
+
', expect_success: false)
|
624
|
+
|
625
|
+
# Unexpected type arguments
|
626
|
+
check_type_of('
|
627
|
+
Kernel.
|
628
|
+
#!arg String
|
629
|
+
puts "hello"
|
630
|
+
', expect_success: false)
|
631
|
+
|
632
|
+
# Passing type arguments through calls
|
633
|
+
check_type_of('
|
634
|
+
module X
|
635
|
+
#: [T] (T) -> T
|
636
|
+
def self.identity(x)
|
637
|
+
x
|
638
|
+
end
|
639
|
+
|
640
|
+
#: [T] (T) -> T
|
641
|
+
def self.indirect_identity(x)
|
642
|
+
#!arg T
|
643
|
+
identity(x)
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
x = X
|
648
|
+
#!arg Integer
|
649
|
+
.indirect_identity(3)
|
650
|
+
', 'x') { |t| t.type == resolve_type('Integer') }
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'understands attr_reader' do
|
654
|
+
check_type_of('
|
655
|
+
#!var @x Integer
|
656
|
+
#!var @y Integer
|
657
|
+
class X
|
658
|
+
#: () -> void
|
659
|
+
def initialize
|
660
|
+
@x = 0
|
661
|
+
@y = 0
|
662
|
+
end
|
663
|
+
|
664
|
+
#: [T] (Symbol, Symbol) -> void
|
665
|
+
#!const required
|
666
|
+
def self.duo_reader(a, b)
|
667
|
+
#!arg T
|
668
|
+
attr_reader a
|
669
|
+
|
670
|
+
#!arg T
|
671
|
+
attr_reader b
|
672
|
+
end
|
673
|
+
|
674
|
+
#!arg Integer
|
675
|
+
duo_reader(:x, :y)
|
676
|
+
end
|
677
|
+
|
678
|
+
x = X.new
|
679
|
+
z = x.x + x.y + 1
|
680
|
+
', 'z') { |t| t.type == resolve_type('Integer') }
|
681
|
+
end
|
682
|
+
|
683
|
+
it 'understands define_method' do
|
684
|
+
# Single usage, return-only
|
685
|
+
check_type_of('
|
686
|
+
class X
|
687
|
+
#!arg Float
|
688
|
+
define_method :pi do
|
689
|
+
3.14
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
p = X.new.pi
|
694
|
+
', 'p') { |t| t.type == resolve_type('Float') }
|
695
|
+
|
696
|
+
# Single usage, parameters
|
697
|
+
check_type_of('
|
698
|
+
class X
|
699
|
+
#!arg Integer
|
700
|
+
#!arg Integer
|
701
|
+
#!arg Integer
|
702
|
+
define_method :add_ints do |a, b|
|
703
|
+
a + b
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
x = X.new.add_ints(3, 2)
|
708
|
+
', 'x') { |t| t.type == resolve_type('Integer') }
|
709
|
+
|
710
|
+
# Looped usage
|
711
|
+
check_type_of('
|
712
|
+
class Adder
|
713
|
+
1000.times do |i,|
|
714
|
+
#!arg Integer
|
715
|
+
#!arg Integer
|
716
|
+
define_method :"add_#{i}" do |input,|
|
717
|
+
i + input
|
718
|
+
end
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
x = Adder.new.add_5(3)
|
723
|
+
', 'x') { |t| t.type == resolve_type('Integer') }
|
724
|
+
|
725
|
+
# Block does not match signature (arity)
|
726
|
+
check_type_of('
|
727
|
+
class X
|
728
|
+
#!arg Float
|
729
|
+
#!arg Float
|
730
|
+
define_method :pi do
|
731
|
+
3.14
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
p = X.new.pi
|
736
|
+
', expect_success: false)
|
737
|
+
check_type_of('
|
738
|
+
class X
|
739
|
+
#!arg Float
|
740
|
+
define_method :pi do |a,|
|
741
|
+
3.14
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
p = X.new.pi
|
746
|
+
', expect_success: false)
|
747
|
+
|
748
|
+
# Block does not match signature (types)
|
749
|
+
check_type_of('
|
750
|
+
class X
|
751
|
+
#!arg Float
|
752
|
+
define_method :pi do
|
753
|
+
"oh no"
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
p = X.new.pi
|
758
|
+
', expect_success: false)
|
759
|
+
|
760
|
+
# Complex example - calling other instance method
|
761
|
+
check_type_of('
|
762
|
+
class Translator
|
763
|
+
#: (String, String) -> String
|
764
|
+
def translate(text, language)
|
765
|
+
"#{text} in #{language} is..."
|
766
|
+
end
|
767
|
+
|
768
|
+
#!arg String
|
769
|
+
[
|
770
|
+
"english", "french", "german", "japanese",
|
771
|
+
"spanish", "urdu", "korean", "hungarian",
|
772
|
+
].each do |lang,|
|
773
|
+
#!arg String
|
774
|
+
#!arg String
|
775
|
+
define_method(:"to_#{lang}") do |s,|
|
776
|
+
translate(s, lang)
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
t = Translator.new
|
782
|
+
x = t.to_german("Hello")
|
783
|
+
', 'x') { |t| t.type == resolve_type('String') }
|
784
|
+
end
|
785
|
+
end
|