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