houndstooth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|