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,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