steep 0.1.0.pre2 → 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 +5 -5
- data/.travis.yml +1 -1
- data/README.md +146 -33
- data/bin/smoke_runner.rb +43 -10
- data/lib/steep/ast/annotation/collection.rb +93 -0
- data/lib/steep/ast/annotation.rb +131 -0
- data/lib/steep/ast/buffer.rb +47 -0
- data/lib/steep/ast/location.rb +82 -0
- data/lib/steep/ast/method_type.rb +116 -0
- data/lib/steep/ast/signature/class.rb +33 -0
- data/lib/steep/ast/signature/const.rb +17 -0
- data/lib/steep/ast/signature/env.rb +123 -0
- data/lib/steep/ast/signature/extension.rb +21 -0
- data/lib/steep/ast/signature/gvar.rb +17 -0
- data/lib/steep/ast/signature/interface.rb +31 -0
- data/lib/steep/ast/signature/members.rb +71 -0
- data/lib/steep/ast/signature/module.rb +21 -0
- data/lib/steep/ast/type_params.rb +13 -0
- data/lib/steep/ast/types/any.rb +39 -0
- data/lib/steep/ast/types/bot.rb +39 -0
- data/lib/steep/ast/types/class.rb +35 -0
- data/lib/steep/ast/types/helper.rb +21 -0
- data/lib/steep/ast/types/instance.rb +39 -0
- data/lib/steep/ast/types/intersection.rb +74 -0
- data/lib/steep/ast/types/name.rb +124 -0
- data/lib/steep/ast/types/self.rb +39 -0
- data/lib/steep/ast/types/top.rb +39 -0
- data/lib/steep/ast/types/union.rb +74 -0
- data/lib/steep/ast/types/var.rb +57 -0
- data/lib/steep/ast/types/void.rb +35 -0
- data/lib/steep/cli.rb +28 -1
- data/lib/steep/drivers/annotations.rb +32 -0
- data/lib/steep/drivers/check.rb +53 -77
- data/lib/steep/drivers/scaffold.rb +303 -0
- data/lib/steep/drivers/utils/each_signature.rb +66 -0
- data/lib/steep/drivers/utils/validator.rb +115 -0
- data/lib/steep/drivers/validate.rb +39 -0
- data/lib/steep/errors.rb +291 -19
- data/lib/steep/interface/abstract.rb +44 -0
- data/lib/steep/interface/builder.rb +470 -0
- data/lib/steep/interface/instantiated.rb +126 -0
- data/lib/steep/interface/ivar_chain.rb +26 -0
- data/lib/steep/interface/method.rb +60 -0
- data/lib/steep/{interface.rb → interface/method_type.rb} +111 -100
- data/lib/steep/interface/substitution.rb +65 -0
- data/lib/steep/module_name.rb +116 -0
- data/lib/steep/parser.rb +1314 -814
- data/lib/steep/parser.y +536 -175
- data/lib/steep/source.rb +220 -25
- data/lib/steep/subtyping/check.rb +673 -0
- data/lib/steep/subtyping/constraints.rb +275 -0
- data/lib/steep/subtyping/relation.rb +41 -0
- data/lib/steep/subtyping/result.rb +126 -0
- data/lib/steep/subtyping/trace.rb +48 -0
- data/lib/steep/subtyping/variable_occurrence.rb +49 -0
- data/lib/steep/subtyping/variable_variance.rb +69 -0
- data/lib/steep/type_construction.rb +1630 -524
- data/lib/steep/type_inference/block_params.rb +100 -0
- data/lib/steep/type_inference/constant_env.rb +55 -0
- data/lib/steep/type_inference/send_args.rb +222 -0
- data/lib/steep/type_inference/type_env.rb +226 -0
- data/lib/steep/type_name.rb +27 -7
- data/lib/steep/typing.rb +4 -0
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +71 -16
- data/smoke/and/a.rb +4 -2
- data/smoke/array/a.rb +4 -5
- data/smoke/array/b.rb +4 -4
- data/smoke/block/a.rb +2 -2
- data/smoke/block/a.rbi +2 -0
- data/smoke/block/b.rb +15 -0
- data/smoke/case/a.rb +3 -3
- data/smoke/class/a.rb +3 -3
- data/smoke/class/b.rb +0 -2
- data/smoke/class/d.rb +2 -2
- data/smoke/class/e.rb +1 -1
- data/smoke/class/f.rb +2 -2
- data/smoke/class/g.rb +8 -0
- data/smoke/const/a.rb +3 -3
- data/smoke/dstr/a.rb +1 -1
- data/smoke/ensure/a.rb +22 -0
- data/smoke/enumerator/a.rb +6 -6
- data/smoke/enumerator/b.rb +22 -0
- data/smoke/extension/a.rb +2 -2
- data/smoke/extension/b.rb +3 -3
- data/smoke/extension/c.rb +1 -1
- data/smoke/hello/hello.rb +2 -2
- data/smoke/if/a.rb +4 -2
- data/smoke/kwbegin/a.rb +8 -0
- data/smoke/literal/a.rb +5 -5
- data/smoke/method/a.rb +5 -5
- data/smoke/method/a.rbi +4 -0
- data/smoke/method/b.rb +29 -0
- data/smoke/module/a.rb +3 -3
- data/smoke/module/a.rbi +9 -0
- data/smoke/module/b.rb +2 -2
- data/smoke/module/c.rb +1 -1
- data/smoke/module/d.rb +5 -0
- data/smoke/module/e.rb +13 -0
- data/smoke/module/f.rb +13 -0
- data/smoke/rescue/a.rb +62 -0
- data/smoke/super/a.rb +2 -2
- data/smoke/type_case/a.rb +35 -0
- data/smoke/yield/a.rb +2 -2
- data/stdlib/builtin.rbi +463 -24
- data/steep.gemspec +3 -2
- metadata +91 -29
- data/lib/steep/annotation.rb +0 -223
- data/lib/steep/signature/class.rb +0 -450
- data/lib/steep/signature/extension.rb +0 -51
- data/lib/steep/signature/interface.rb +0 -49
- data/lib/steep/types/any.rb +0 -31
- data/lib/steep/types/class.rb +0 -27
- data/lib/steep/types/instance.rb +0 -27
- data/lib/steep/types/merge.rb +0 -32
- data/lib/steep/types/name.rb +0 -57
- data/lib/steep/types/union.rb +0 -42
- data/lib/steep/types/var.rb +0 -38
- data/lib/steep/types.rb +0 -4
data/lib/steep/errors.rb
CHANGED
@@ -8,18 +8,49 @@ module Steep
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def location_to_str
|
11
|
-
"#{node.loc.expression.source_buffer.name}:#{node.loc.first_line}:#{node.loc.column}"
|
11
|
+
Rainbow("#{node.loc.expression.source_buffer.name}:#{node.loc.first_line}:#{node.loc.column}").red
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_to(io)
|
15
|
+
source = node.loc.expression.source
|
16
|
+
io.puts "#{to_s} (#{Rainbow(source.split(/\n/).first).blue})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ResultPrinter
|
21
|
+
def print_result_to(io, level: 2)
|
22
|
+
indent = " " * level
|
23
|
+
result.trace.each do |s, t|
|
24
|
+
case s
|
25
|
+
when Interface::Method
|
26
|
+
io.puts "#{indent}#{s.name}(#{s.type_name}) <: #{t.name}(#{t.type_name})"
|
27
|
+
when Interface::MethodType
|
28
|
+
io.puts "#{indent}#{s} <: #{t} (#{s.location.name}:#{s.location.start_line})"
|
29
|
+
else
|
30
|
+
io.puts "#{indent}#{s} <: #{t}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
io.puts "#{indent} #{result.error.message}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_to(io)
|
37
|
+
super
|
38
|
+
print_result_to io
|
12
39
|
end
|
13
40
|
end
|
14
41
|
|
15
42
|
class IncompatibleAssignment < Base
|
16
43
|
attr_reader :lhs_type
|
17
44
|
attr_reader :rhs_type
|
45
|
+
attr_reader :result
|
46
|
+
|
47
|
+
include ResultPrinter
|
18
48
|
|
19
|
-
def initialize(node:, lhs_type:, rhs_type:)
|
49
|
+
def initialize(node:, lhs_type:, rhs_type:, result:)
|
20
50
|
super(node: node)
|
21
51
|
@lhs_type = lhs_type
|
22
52
|
@rhs_type = rhs_type
|
53
|
+
@result = result
|
23
54
|
end
|
24
55
|
|
25
56
|
def to_s
|
@@ -27,18 +58,66 @@ module Steep
|
|
27
58
|
end
|
28
59
|
end
|
29
60
|
|
61
|
+
class IncompatibleArguments < Base
|
62
|
+
attr_reader :node
|
63
|
+
attr_reader :receiver_type
|
64
|
+
attr_reader :method_type
|
65
|
+
|
66
|
+
def initialize(node:, receiver_type:, method_type:)
|
67
|
+
super(node: node)
|
68
|
+
@receiver_type = receiver_type
|
69
|
+
@method_type = method_type
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"#{location_to_str}: IncompatibleArguments: receiver=#{receiver_type}, method_type=#{method_type}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
30
77
|
class ArgumentTypeMismatch < Base
|
31
|
-
attr_reader :
|
32
|
-
attr_reader :
|
78
|
+
attr_reader :node
|
79
|
+
attr_reader :expected
|
80
|
+
attr_reader :actual
|
81
|
+
attr_reader :receiver_type
|
33
82
|
|
34
|
-
def initialize(node:,
|
83
|
+
def initialize(node:, receiver_type:, expected:, actual:)
|
35
84
|
super(node: node)
|
36
|
-
@
|
37
|
-
@
|
85
|
+
@receiver_type = receiver_type
|
86
|
+
@expected = expected
|
87
|
+
@actual = actual
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
"#{location_to_str}: ArgumentTypeMismatch: receiver=#{receiver_type}, expected=#{expected}, actual=#{actual}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class IncompatibleBlockParameters < Base
|
96
|
+
attr_reader :node
|
97
|
+
attr_reader :method_type
|
98
|
+
|
99
|
+
def initialize(node:, method_type:)
|
100
|
+
super(node: node)
|
101
|
+
@method_type = method_type
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
"#{location_to_str}: IncompatibleBlockParameters: method_type=#{method_type}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class BlockParameterTypeMismatch < Base
|
110
|
+
attr_reader :expected
|
111
|
+
attr_reader :actual
|
112
|
+
|
113
|
+
def initialize(node:, expected:, actual:)
|
114
|
+
super(node: node)
|
115
|
+
@expected = expected
|
116
|
+
@actual = actual
|
38
117
|
end
|
39
118
|
|
40
119
|
def to_s
|
41
|
-
"#{location_to_str}:
|
120
|
+
"#{location_to_str}: BlockParameterTypeMismatch: expected=#{expected}, actual=#{actual}"
|
42
121
|
end
|
43
122
|
end
|
44
123
|
|
@@ -60,11 +139,15 @@ module Steep
|
|
60
139
|
class ReturnTypeMismatch < Base
|
61
140
|
attr_reader :expected
|
62
141
|
attr_reader :actual
|
142
|
+
attr_reader :result
|
63
143
|
|
64
|
-
|
144
|
+
include ResultPrinter
|
145
|
+
|
146
|
+
def initialize(node:, expected:, actual:, result:)
|
65
147
|
super(node: node)
|
66
148
|
@expected = expected
|
67
149
|
@actual = actual
|
150
|
+
@result = result
|
68
151
|
end
|
69
152
|
|
70
153
|
def to_s
|
@@ -73,24 +156,43 @@ module Steep
|
|
73
156
|
end
|
74
157
|
|
75
158
|
class UnexpectedBlockGiven < Base
|
76
|
-
attr_reader :
|
77
|
-
attr_reader :type
|
159
|
+
attr_reader :method_type
|
78
160
|
|
79
|
-
def initialize(node:,
|
161
|
+
def initialize(node:, method_type:)
|
80
162
|
super(node: node)
|
81
|
-
@
|
82
|
-
|
163
|
+
@method_type = method_type
|
164
|
+
end
|
165
|
+
|
166
|
+
def to_s
|
167
|
+
"#{location_to_str}: UnexpectedBlockGiven: method_type=#{method_type.location&.source}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class RequiredBlockMissing < Base
|
172
|
+
attr_reader :method_type
|
173
|
+
|
174
|
+
def initialize(node:, method_type:)
|
175
|
+
super(node: node)
|
176
|
+
@method_type = method_type
|
177
|
+
end
|
178
|
+
|
179
|
+
def to_s
|
180
|
+
"#{location_to_str}: RequiredBlockMissing: method_type=#{method_type.location&.source}"
|
83
181
|
end
|
84
182
|
end
|
85
183
|
|
86
184
|
class BlockTypeMismatch < Base
|
87
185
|
attr_reader :expected
|
88
186
|
attr_reader :actual
|
187
|
+
attr_reader :result
|
89
188
|
|
90
|
-
|
189
|
+
include ResultPrinter
|
190
|
+
|
191
|
+
def initialize(node:, expected:, actual:, result:)
|
91
192
|
super(node: node)
|
92
193
|
@expected = expected
|
93
194
|
@actual = actual
|
195
|
+
@result = result
|
94
196
|
end
|
95
197
|
|
96
198
|
def to_s
|
@@ -101,28 +203,103 @@ module Steep
|
|
101
203
|
class BreakTypeMismatch < Base
|
102
204
|
attr_reader :expected
|
103
205
|
attr_reader :actual
|
206
|
+
attr_reader :result
|
104
207
|
|
105
|
-
|
208
|
+
include ResultPrinter
|
209
|
+
|
210
|
+
def initialize(node:, expected:, actual:, result:)
|
106
211
|
super(node: node)
|
107
212
|
@expected = expected
|
108
213
|
@actual = actual
|
214
|
+
@result = result
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
"#{location_to_str}: BreakTypeMismatch: expected=#{expected}, actual=#{actual}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class UnexpectedJump < Base
|
223
|
+
def to_s
|
224
|
+
"#{location_to_str}: UnexpectedJump"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class UnexpectedJumpValue < Base
|
229
|
+
def to_s
|
230
|
+
"#{location_to_str}: UnexpectedJumpValue"
|
109
231
|
end
|
110
232
|
end
|
111
233
|
|
112
|
-
class
|
234
|
+
class MethodArityMismatch < Base
|
113
235
|
def to_s
|
114
|
-
"#{location_to_str}:
|
236
|
+
"#{location_to_str}: MethodArityMismatch: method=#{node.children[0]}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class IncompatibleMethodTypeAnnotation < Base
|
241
|
+
attr_reader :interface_method
|
242
|
+
attr_reader :annotation_method
|
243
|
+
attr_reader :result
|
244
|
+
|
245
|
+
include ResultPrinter
|
246
|
+
|
247
|
+
def initialize(node:, interface_method:, annotation_method:, result:)
|
248
|
+
super(node: node)
|
249
|
+
@interface_method = interface_method
|
250
|
+
@annotation_method = annotation_method
|
251
|
+
@result = result
|
252
|
+
end
|
253
|
+
|
254
|
+
def to_s
|
255
|
+
"#{location_to_str}: IncompatibleMethodTypeAnnotation: interface_method=#{interface_method.type_name}.#{interface_method.name}, annotation_method=#{annotation_method.name}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class MethodDefinitionWithOverloading < Base
|
260
|
+
attr_reader :method
|
261
|
+
|
262
|
+
def initialize(node:, method:)
|
263
|
+
super(node: node)
|
264
|
+
@method = method
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_s
|
268
|
+
"#{location_to_str}: MethodDefinitionWithOverloading: method=#{method.name}, types=#{method.types.join(" | ")}"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class MethodReturnTypeAnnotationMismatch < Base
|
273
|
+
attr_reader :method_type
|
274
|
+
attr_reader :annotation_type
|
275
|
+
attr_reader :result
|
276
|
+
|
277
|
+
include ResultPrinter
|
278
|
+
|
279
|
+
def initialize(node:, method_type:, annotation_type:, result:)
|
280
|
+
super(node: node)
|
281
|
+
@method_type = method_type
|
282
|
+
@annotation_type = annotation_type
|
283
|
+
@result = result
|
284
|
+
end
|
285
|
+
|
286
|
+
def to_s
|
287
|
+
"#{location_to_str}: MethodReturnTypeAnnotationMismatch: method_type=#{method_type.return_type}, annotation_type=#{annotation_type}"
|
115
288
|
end
|
116
289
|
end
|
117
290
|
|
118
291
|
class MethodBodyTypeMismatch < Base
|
119
292
|
attr_reader :expected
|
120
293
|
attr_reader :actual
|
294
|
+
attr_reader :result
|
121
295
|
|
122
|
-
|
296
|
+
include ResultPrinter
|
297
|
+
|
298
|
+
def initialize(node:, expected:, actual:, result:)
|
123
299
|
super(node: node)
|
124
300
|
@expected = expected
|
125
301
|
@actual = actual
|
302
|
+
@result = result
|
126
303
|
end
|
127
304
|
|
128
305
|
def to_s
|
@@ -194,6 +371,19 @@ module Steep
|
|
194
371
|
end
|
195
372
|
end
|
196
373
|
|
374
|
+
class UnknownConstantAssigned < Base
|
375
|
+
attr_reader :type
|
376
|
+
|
377
|
+
def initialize(node:, type:)
|
378
|
+
super(node: node)
|
379
|
+
@type = type
|
380
|
+
end
|
381
|
+
|
382
|
+
def to_s
|
383
|
+
"#{location_to_str}: UnknownConstantAssigned: type=#{type}"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
197
387
|
class FallbackAny < Base
|
198
388
|
def initialize(node:)
|
199
389
|
@node = node
|
@@ -203,5 +393,87 @@ module Steep
|
|
203
393
|
"#{location_to_str}: FallbackAny"
|
204
394
|
end
|
205
395
|
end
|
396
|
+
|
397
|
+
class UnsatisfiableConstraint < Base
|
398
|
+
attr_reader :method_type
|
399
|
+
attr_reader :var
|
400
|
+
attr_reader :sub_type
|
401
|
+
attr_reader :super_type
|
402
|
+
attr_reader :result
|
403
|
+
|
404
|
+
def initialize(node:, method_type:, var:, sub_type:, super_type:, result:)
|
405
|
+
super(node: node)
|
406
|
+
@method_type = method_type
|
407
|
+
@var = var
|
408
|
+
@sub_type = sub_type
|
409
|
+
@super_type = super_type
|
410
|
+
@result = result
|
411
|
+
end
|
412
|
+
|
413
|
+
include ResultPrinter
|
414
|
+
|
415
|
+
def to_s
|
416
|
+
"#{location_to_str}: UnsatisfiableConstraint: method_type=#{method_type}, constraint=#{sub_type} <: '#{var} <: #{super_type}"
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
class IncompatibleAnnotation < Base
|
421
|
+
attr_reader :var_name
|
422
|
+
attr_reader :result
|
423
|
+
attr_reader :relation
|
424
|
+
|
425
|
+
def initialize(node:, var_name:, result:, relation:)
|
426
|
+
super(node: node)
|
427
|
+
@var_name = var_name
|
428
|
+
@result = result
|
429
|
+
@relation = relation
|
430
|
+
end
|
431
|
+
|
432
|
+
include ResultPrinter
|
433
|
+
|
434
|
+
def to_s
|
435
|
+
"#{location_to_str}: IncompatibleAnnotation: var_name=#{var_name}, #{relation}"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
class IncompatibleTypeCase < Base
|
440
|
+
attr_reader :var_name
|
441
|
+
attr_reader :result
|
442
|
+
attr_reader :relation
|
443
|
+
|
444
|
+
def initialize(node:, var_name:, result:, relation:)
|
445
|
+
super(node: node)
|
446
|
+
@var_name = var_name
|
447
|
+
@result = result
|
448
|
+
@relation = relation
|
449
|
+
end
|
450
|
+
|
451
|
+
include ResultPrinter
|
452
|
+
|
453
|
+
def to_s
|
454
|
+
"#{location_to_str}: IncompatibleTypeCase: var_name=#{var_name}, #{relation}"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class ElseOnExhaustiveCase < Base
|
459
|
+
def initialize(node:, type:)
|
460
|
+
def to_s
|
461
|
+
"#{location_to_str}: ElseOnExhaustiveCase: type=#{type}"
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
class UnexpectedSplat < Base
|
467
|
+
attr_reader :type
|
468
|
+
|
469
|
+
def initialize(node:, type:)
|
470
|
+
super(node: node)
|
471
|
+
@type = type
|
472
|
+
end
|
473
|
+
|
474
|
+
def to_s
|
475
|
+
"#{location_to_str}: UnexpectedSplat: type=#{type}"
|
476
|
+
end
|
477
|
+
end
|
206
478
|
end
|
207
479
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Steep
|
2
|
+
module Interface
|
3
|
+
class Abstract
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :kind
|
6
|
+
attr_reader :params
|
7
|
+
attr_reader :methods
|
8
|
+
attr_reader :supers
|
9
|
+
attr_reader :ivar_chains
|
10
|
+
|
11
|
+
def initialize(name:, params:, methods:, supers:, ivar_chains:)
|
12
|
+
@name = name
|
13
|
+
@params = params
|
14
|
+
@methods = methods
|
15
|
+
@supers = supers
|
16
|
+
@ivar_chains = ivar_chains
|
17
|
+
end
|
18
|
+
|
19
|
+
def ivars
|
20
|
+
@ivars ||= ivar_chains.transform_values(&:type)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
other.is_a?(self.class) &&
|
25
|
+
other.name == name &&
|
26
|
+
other.params == params &&
|
27
|
+
other.methods == methods &&
|
28
|
+
other.supers == supers &&
|
29
|
+
other.ivars == ivars
|
30
|
+
end
|
31
|
+
|
32
|
+
def instantiate(type:, args:, instance_type:, module_type:)
|
33
|
+
Steep.logger.debug("type=#{type}, self=#{name}, args=#{args}, params=#{params}")
|
34
|
+
subst = Substitution.build(params, args, instance_type: instance_type, module_type: module_type, self_type: type)
|
35
|
+
|
36
|
+
Instantiated.new(
|
37
|
+
type: type,
|
38
|
+
methods: methods.transform_values {|method| method.subst(subst) },
|
39
|
+
ivar_chains: ivar_chains.transform_values {|chain| chain.subst(subst) }
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|