solidity-typed 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.
@@ -0,0 +1,492 @@
1
+
2
+ ###
3
+ # Metatype classes like Class in ruby
4
+ # every Typed<> has a (Meta)Type class singleton (shared) instance that is_a?( Type )
5
+
6
+
7
+ ## note:
8
+ ## make bytes a reference type?
9
+ ## bytes NOT like string frozen?
10
+ ## more like a stringbuffer / bytesbuffer -
11
+ ## double check if "in-place" updates to value possible!!!
12
+
13
+
14
+
15
+ ###
16
+ # global helper(s) - move to ??? - why? why not?
17
+
18
+ def _sanitize_class_name( name )
19
+ name = name.sub( /\bContractBase::/, '' ) ## remove contract module from name if present
20
+ name = name.sub( /\bContract::/, '' ) ## remove contract module from name if present
21
+ name = name.sub( /\bTyped::/, '' )
22
+ name = name.sub( /\bTypes::/, '' )
23
+ name = name.sub( /\bTypedArray::/, '' )
24
+ name = name.sub( /\bTypedMapping::/, '' )
25
+ name = name.gsub( '::', '' ) ## remove module separator if present
26
+ name
27
+ end
28
+
29
+
30
+
31
+ module Types
32
+ class Typed ## note: use class Typed as namespace (all metatype etc. nested here - the beginning)
33
+
34
+ #######
35
+ ## global literal constants
36
+ ## add LITERAL_ eg. LITERAL_STRING_ZERO - why? why not?
37
+
38
+ STRING_ZERO = ''.freeze ## string with utf-8 encoding
39
+ BYTES_ZERO = String.new().freeze ## string with binary encoding
40
+ BYTES20_ZERO = ('0x'+'00'*20).freeze ## 20 bytes (40 hexchars) ## care about string encoding here - why? why not?
41
+ BYTES32_ZERO = ('0x'+'00'*32).freeze ## 32 bytes (64 hexchars)
42
+
43
+ ADDRESS_ZERO = BYTES20_ZERO
44
+ INSCRIPTION_ID_ZERO = INSCRIPTIONID_ZERO = BYTES32_ZERO
45
+
46
+
47
+
48
+ class Type
49
+ ## change format to type or typesig/type_sig
50
+ ## or sig or signature or typespec - why? why not?
51
+ def format
52
+ ## check/todo: what error to raise for not implemented / method not defined???
53
+ ### note: raise (will use RuntimeError/Exception?)
54
+ raise "no required format method defined for Type subclass #{self.class.name}; sorry"
55
+ end
56
+ ## return type signature string - why? why not?
57
+ ## e.g. string
58
+ ## mapping(address=>uint256)
59
+ ## string[]
60
+ ## and so on...
61
+ def pretty_print( printer ) printer.text( "<type #{format}>" ); end
62
+
63
+ def zero ## change to zero_literal (returns 0, [], {} - NOT typed version) - why? why not?
64
+ # ## check/todo: what error to raise for not implemented / method not defined???
65
+ raise "no required zero method defined for Type subclass #{self.class.name}; sorry"
66
+ end
67
+
68
+ def mut? # is mutable (read/write)
69
+ raise "no require mut(ability)? helper for Type subclass #{self.class.name}; sorry"
70
+ end
71
+
72
+
73
+ # ## todo/check - make "dynamic" e.g. structs with all value types still value type? - why? why not?
74
+ # ## check if is_value_type is used anywhere? remove!!!! - why? why not?
75
+ # def is_value_type?() is_a?( ValueType ); end
76
+
77
+
78
+ def mapping?() is_a?( MappingType ); end
79
+ def array?() is_a?( ArrayType ); end
80
+ ## add more metatype helpers here - why? why not?
81
+
82
+
83
+ ### todo/check for minimal required methods required for
84
+ ## compare and equal support??
85
+ def ==(other)
86
+ raise "no required == method defined for Type subclass #{self.class.name}; sorry"
87
+ end
88
+
89
+
90
+ ######
91
+ ## note: format MUST be unique for types
92
+ ## if self.format == other.format than same type
93
+ ## use for hash?
94
+ ## check default eql? is self.object_id == other.object_id ???
95
+ ### change to hash == other.hash; why? why not?
96
+
97
+ def hash() [format].hash; end ## add Type prefix or such - why? why not?
98
+ def eql?(other) hash == other.hash; end ## check eql? used for what?
99
+ end # class Type
100
+
101
+
102
+ ## use for (abstract) data types
103
+ ## e.g. bool, enums - why? why not?
104
+ ## can only (re)use constants (true|false or defined enums)
105
+ ## BUT not any new values!!!!
106
+ class DataType < Type; end
107
+
108
+ class ValueType < Type; end ## add value & reference type base - why? why not?
109
+
110
+ class ReferenceType < Type; end
111
+
112
+
113
+ ##
114
+ ## ruby note: is_a? and kind_of? are alias - the same
115
+ ## for "strict" checking use instance_of?
116
+
117
+
118
+ class StringType < ValueType ## note: strings are frozen / immutable - check again!!
119
+
120
+ def self.instance() @instance ||= new; end
121
+
122
+
123
+ def format() 'string'; end
124
+ alias_method :to_s, :format
125
+
126
+ def ==(other) other.is_a?( StringType ); end
127
+
128
+
129
+ ## note: use Types::String here to avoid confusion with ::String - why? why not?
130
+ ### -fix-fix-fix- remove typedclass for "primitives" - used anywhere - why? why not?
131
+ def typedclass_name() Types::String.name; end
132
+ def typedclass() Types::String; end
133
+
134
+ def mut?() false; end
135
+ def zero() Types::String.zero; end
136
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
137
+ def new( initial_value ) Types::String.new( initial_value ); end
138
+ end
139
+
140
+
141
+
142
+ class AddressType < ValueType
143
+
144
+ def self.instance() @instance ||= new; end
145
+
146
+ def format() 'address'; end
147
+ alias_method :to_s, :format
148
+
149
+ def ==(other) other.is_a?( AddressType ); end
150
+
151
+ def typedclass_name() Address.name; end
152
+ def typedclass() Address; end
153
+
154
+ def mut?() false; end
155
+ def zero() Address.zero; end
156
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
157
+ def new( initial_value ) Address.new( initial_value ); end
158
+ end
159
+
160
+
161
+
162
+
163
+ class InscriptionIdType < ValueType ## todo/check: rename to inscripeId or inscriptionId
164
+
165
+ def self.instance() @instance ||= new; end
166
+
167
+ def format() 'inscriptionId'; end
168
+ alias_method :to_s, :format
169
+
170
+ def ==(other) other.is_a?( InscriptionIdType ); end
171
+
172
+ def typedclass_name() InscriptionId.name; end
173
+ def typedclass() InscriptionId; end
174
+
175
+ def mut?() false; end
176
+ def zero() InscriptionId.zero; end
177
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
178
+ def new( initial_value ) InscriptionId.new( initial_value ); end
179
+ end
180
+
181
+
182
+ class Bytes32Type < ValueType
183
+ def self.instance() @instance ||= new; end
184
+
185
+
186
+ def format() 'bytes32'; end
187
+ alias_method :to_s, :format
188
+
189
+ def ==(other) other.is_a?( Bytes32Type ); end
190
+
191
+
192
+ def typedclass_name() Bytes32.name; end
193
+ def typedclass() Bytes32; end
194
+
195
+ def mut?() false; end
196
+ def zero() Bytes32.zero; end
197
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
198
+ def new( initial_value ) Bytes32.new( initial_value ); end
199
+ end
200
+
201
+
202
+ class BytesType < ValueType ### fix: see comments above - is bytes dynamic? or frozen?
203
+ def self.instance() @instance ||= new; end
204
+
205
+ def format() 'bytes'; end
206
+ alias_method :to_s, :format
207
+
208
+ def ==(other) other.is_a?( BytesType ); end
209
+
210
+ def typedclass_name() Bytes.name; end
211
+ def typedclass() Bytes; end
212
+
213
+ def mut?() false; end
214
+ def zero() Bytes.zero; end
215
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
216
+ def new( initial_value ) Bytes.new( initial_value ); end
217
+ end
218
+
219
+
220
+ class UIntType < ValueType
221
+ def self.instance() @instance ||= new; end
222
+
223
+ def format() 'uint'; end
224
+ alias_method :to_s, :format
225
+
226
+ ## note abi requires uint256!!! (not uint)
227
+ ## todo/check - rename to sig or abisig or selector or ???
228
+ def abi() 'uint256'; end
229
+
230
+ def ==(other) other.is_a?( UIntType ); end
231
+
232
+
233
+ def typedclass_name() UInt.name; end
234
+ def typedclass() UInt; end
235
+
236
+ def mut?() false; end
237
+ def zero() UInt.zero; end
238
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
239
+ def new( initial_value ) UInt.new( initial_value ); end
240
+ end
241
+
242
+
243
+ class IntType < ValueType
244
+ def self.instance() @instance ||= new; end
245
+
246
+ def format() 'int'; end
247
+ alias_method :to_s, :format
248
+
249
+ ## note abi requires uint256!!! (not uint)
250
+ ## todo/check - rename to sig or abisig or selector or ???
251
+ def abi() 'int256'; end
252
+
253
+
254
+ def ==(other) other.is_a?( IntType ); end
255
+
256
+
257
+ def typedclass_name() Int.name; end
258
+ def typedclass() Int; end
259
+
260
+ def mut?() false; end
261
+ def zero() Int.zero; end
262
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
263
+ def new( initial_value ) Int.new( initial_value ); end
264
+ end
265
+
266
+
267
+ class TimestampType < ValueType ## note: datetime is int (epoch time since 1970 in seconds in utc)
268
+ def self.instance() @instance ||= new; end
269
+
270
+ def format() 'timestamp'; end
271
+ alias_method :to_s, :format
272
+
273
+ ## check for abi sig format is it uint32 ???
274
+
275
+ def ==(other) other.is_a?( TimestampType ); end
276
+
277
+
278
+ def typedclass_name() Timestamp.name; end
279
+ def typedclass() Timestamp; end
280
+
281
+ def mut?() false; end
282
+ def zero() Timestamp.zero; end
283
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
284
+ def new( initial_value ) Timestamp.new( initial_value ); end
285
+ end
286
+
287
+
288
+ class TimedeltaType < ValueType
289
+ def self.instance() @instance ||= new; end
290
+
291
+ def format() 'timedelta'; end
292
+ alias_method :to_s, :format
293
+
294
+ def ==(other) other.is_a?( TimedeltaType ); end
295
+
296
+
297
+ def typedclass_name() Timedelta.name; end
298
+ def typedclass() Timedelta; end
299
+
300
+ def mut?() false; end
301
+ def zero() Timedelta.zero; end
302
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
303
+ def new( initial_value ) Timedelta.new( initial_value ); end
304
+ end
305
+
306
+
307
+
308
+
309
+
310
+ ### note: for bool and enum use DataType or AbstractDataType (ADT)
311
+ ## values must always be from existing set (CANNOT create new values/ones)
312
+ ## all instances are immutable/frozen and shared
313
+ class EnumType < Type
314
+ attr_reader :enum_name
315
+ attr_reader :enum_class ## reference enum_class here - why? why not?
316
+ def initialize( enum_name, enum_class )
317
+ @enum_name = enum_name
318
+ @enum_class = enum_class
319
+ end
320
+ def format
321
+ "#{enum_name} enum(#{enum_class.keys.join(',')})"
322
+ end
323
+ alias_method :to_s, :format
324
+
325
+ ## note abi requires uint8!!! 0-255 (8bit)
326
+ ## todo/check - rename to sig or abisig or selector or ???
327
+ def abi() 'uint8'; end
328
+
329
+
330
+ def ==(other)
331
+ other.is_a?( EnumType ) &&
332
+ @enum_name == other.enum_name && ## check for name too - why? why not?
333
+ @enum_class == other.enum_class
334
+ end
335
+
336
+
337
+ def typedclass_name() @enum_class.name; end
338
+ def typedclass() @enum_class; end
339
+
340
+ def mut?() false; end
341
+ def zero() @enum_class.zero; end
342
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
343
+ def new( initial_value )
344
+ ## allow new use here - why? why not?
345
+ @enum_class.members[ initial_value ]
346
+ end
347
+ end
348
+
349
+
350
+
351
+ class StructType < ReferenceType
352
+ attr_reader :struct_name
353
+ attr_reader :struct_class ## reference struct_class here - why? why not?
354
+ def initialize( struct_name, struct_class )
355
+ @struct_name = struct_name
356
+ @struct_class = struct_class
357
+ end
358
+ def format
359
+ ## use tuple here (not struct) - why? why not?
360
+ named_types = @struct_class.attributes.map {|key,type| "#{key} #{type.format}" }
361
+ "#{@struct_name} struct(#{named_types.join(',')})"
362
+ end
363
+ alias_method :to_s, :format
364
+
365
+
366
+ ### note: abi requires "tuple()"
367
+ def abi
368
+ types = @struct_class.attributes.map {|_,type| type.abi }
369
+ "tuples(#{types.join(',')})"
370
+ end
371
+
372
+ def ==(other)
373
+ other.is_a?( StructType ) &&
374
+ @struct_name == other.struct_name && ## check for name too - why? why not?
375
+ @struct_class == other.struct_class
376
+ end
377
+
378
+
379
+ def typedclass_name() @struct_class.name; end
380
+ def typedclass() @struct_class; end
381
+
382
+ ## note: mut? == true MUST use new_zero (dup)
383
+ ## mut? == false MUST use zero (frozen/shared/singelton)
384
+ def mut?() true; end
385
+ def new_zero() @struct_class.new_zero; end
386
+ def new( initial_values ) ## todo/check: change to values with splat - why? why not?
387
+ ## note: use "splat" here - must be empty or matching number of fields/attributes
388
+ ## change - why? why not?
389
+ @struct_class.new( *initial_values )
390
+ end
391
+ end # class StructType
392
+
393
+
394
+
395
+ ###
396
+ # event for now kind of like a struct - why? why not?
397
+ ## but MUST be initialized (and than frozen)
398
+ ## and no zero possible etc.
399
+
400
+ class EventType < ReferenceType
401
+ attr_reader :event_name
402
+ attr_reader :event_class ## reference event_class here - why? why not?
403
+ def initialize( event_name, event_class )
404
+ @event_name = event_name
405
+ @event_class = event_class
406
+ end
407
+ def format
408
+ ## use tuple here (not event) - why? why not?
409
+ named_types = @event_class.attributes.map {|key,type| "#{key} #{type.format}" }
410
+ "#{@event_name} event(#{named_types.join(',')})"
411
+ end
412
+ alias_method :to_s, :format
413
+
414
+ ## check what abi looks like if possible for event
415
+ ## is like tuple?
416
+
417
+ def ==(other)
418
+ other.is_a?( EventType ) &&
419
+ @event_name == other.event_name && ## check for name too - why? why not?
420
+ @event_class == other.event_class
421
+ end
422
+
423
+
424
+ def typedclass_name() @event_class.name; end
425
+ def typedclass() @event_class; end
426
+
427
+ ## note: mut? == true MUST use new_zero (dup)
428
+ ## mut? == false MUST use zero (frozen/shared/singelton)
429
+ def mut?() false; end
430
+ def zero
431
+ raise "event cannot be zero (by defintion); sorry"
432
+ end
433
+ alias_method :new_zero, :zero
434
+
435
+ def new( initial_values ) ## todo/check: change to values with splat - why? why not?
436
+ ## note: use "splat" here - must be empty or matching number of fields/attributes
437
+ ## change - why? why not?
438
+ @event_class.new( *initial_values )
439
+ end
440
+ end # class EventType
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+ ## todo/check
450
+ ## keep (internal) contract type??
451
+ ## - raise error on create?
452
+ ## - check what operations to support
453
+ ## - is like address (value type??)
454
+
455
+
456
+
457
+ class ContractType < ValueType
458
+
459
+ def self.instance( contract_type )
460
+ raise ArgumentError, "[ContractType.insntance] class expected for contract_type arg" unless contract_type.is_a?( Class )
461
+ @instances ||= {}
462
+ @instances[ contract_type.name ] ||= new( contract_type )
463
+ end
464
+
465
+ attr_reader :contract_type
466
+ ## note: assume for now contract_type is a (contract) class!!!!!
467
+ def initialize( contract_type )
468
+ raise ArgumentError, "[ContractType#initialize] class expected for contract_type arg" unless contract_type.is_a?( Class )
469
+ @contract_type = contract_type
470
+ end
471
+
472
+ def format() "contract(#{@contract_type.name})"; end
473
+ alias_method :to_s, :format
474
+
475
+ def ==(other)
476
+ other.is_a?( ContractType ) &&
477
+ @contract_type == other.contract_type
478
+ end
479
+
480
+
481
+ def mut?() false; end
482
+ ## add support with passed in address - why? why not?
483
+ def new( initial_value )
484
+ raise NameError, "no method create for ContractType; sorry"
485
+ end
486
+ end # class ContractType
487
+
488
+
489
+ ## note: use class Typed as namespace (all metatype etc. nested here - the end)
490
+ end # class Typed
491
+
492
+ end ## module Types
@@ -0,0 +1,108 @@
1
+ module Types
2
+
3
+
4
+ class UInt < TypedValue
5
+ def self.type() UIntType.instance; end
6
+ def self.zero() @zero ||= UInt.new; end
7
+ def zero?() @value == 0; end
8
+
9
+ def initialize( initial_value = 0 )
10
+ ## was: initial_value ||= type.zero
11
+ ## check if nil gets passed in - default not used?
12
+
13
+ raise ArgumentError, "expected literal of type #{type}; got typed #{initial_value.pretty_print_inspect}" if initial_value.is_a?( Typed )
14
+
15
+ @value = type.check_and_normalize_literal( initial_value )
16
+ @value.freeze ## freeze here (and freeze self!) - why? why not?
17
+ @value
18
+ end
19
+
20
+ include Comparable
21
+ def <=>(other) @value <=> other.to_int; end
22
+
23
+ def +(other ) UInt.new( @value + other.to_int); end
24
+ def -(other) UInt.new( @value - other.to_int); end
25
+ def *(other) UInt.new( @value * other.to_int); end
26
+ def /(other) UInt.new( @value / other.to_int); end
27
+ ## add more Integer forwards here!!!!
28
+ ##def_delegators :@value, :+, :-
29
+
30
+ ##
31
+ ## undefined method `>=' for #<UInt @value=21000000> (NoMethodError)
32
+ ## undefined method `-' for #<UInt @value=21000000> (NoMethodError)
33
+ ## def to_i() @value; end
34
+ def to_int() @value; end ## "automagilally" support implicit integer conversion - why? why not?
35
+ def to_i() @value; end
36
+ end # class UInt
37
+
38
+
39
+
40
+ class Int < TypedValue
41
+ def self.type() IntType.instance; end
42
+ def self.zero() @zero ||= Int.new; end
43
+ def zero?() @value == 0; end
44
+
45
+ def initialize( initial_value = 0 )
46
+ ## was: initial_value ||= type.zero
47
+ ## check if nil gets passed in - default not used?
48
+
49
+ raise ArgumentError, "expected literal of type #{type}; got typed #{initial_value.pretty_print_inspect}" if initial_value.is_a?( Typed )
50
+
51
+ @value = type.check_and_normalize_literal( initial_value )
52
+ @value.freeze ## freeze here (and freeze self!) - why? why not?
53
+ @value
54
+ end
55
+
56
+ include Comparable
57
+ def <=>(other) @value <=> other.to_int; end
58
+
59
+ def +(other ) Int.new( @value + other.to_int); end
60
+ def -(other) Int.new( @value - other.to_int); end
61
+ def *(other) Int.new( @value * other.to_int); end
62
+ def /(other) Int.new( @value / other.to_int); end
63
+
64
+
65
+ def to_int() @value; end ## "automagilally" support implicit integer conversion - why? why not?
66
+ def to_i() @value; end
67
+ end # class Int
68
+
69
+
70
+
71
+ class Timestamp < TypedValue
72
+ def self.type() TimestampType.instance; end
73
+ def self.zero() @zero ||= new; end
74
+ def zero?() @value == 0; end
75
+
76
+ def initialize( initial_value = 0 )
77
+ ## was: initial_value ||= type.zero
78
+ ## check if nil gets passed in - default not used?
79
+
80
+ raise ArgumentError, "expected literal of type #{type}; got typed #{initial_value.pretty_print_inspect}" if initial_value.is_a?( Typed )
81
+
82
+ @value = type.check_and_normalize_literal( initial_value )
83
+ @value.freeze ## freeze here (and freeze self!) - why? why not?
84
+ @value
85
+ end
86
+ end # class Timestamp
87
+
88
+
89
+ class Timedelta < TypedValue
90
+ def self.type() TimedeltaType.instance; end
91
+ def self.zero() @zero ||= new; end
92
+ def zero?() @value == 0; end
93
+
94
+ def initialize( initial_value = 0 )
95
+ ## was: initial_value ||= type.zero
96
+ ## check if nil gets passed in - default not used?
97
+
98
+ raise ArgumentError, "expected literal of type #{type}; got typed #{initial_value.pretty_print_inspect}" if initial_value.is_a?( Typed )
99
+
100
+ @value = type.check_and_normalize_literal( initial_value )
101
+ @value.freeze ## freeze here (and freeze self!) - why? why not?
102
+ @value
103
+ end
104
+ end # class Timedelta
105
+
106
+
107
+ end # module Types
108
+
@@ -0,0 +1,73 @@
1
+
2
+ module Types
3
+ class Struct < Typed
4
+
5
+ def self.zero
6
+ ## note: freeze return new zero (for "singelton" & "immutable" zero instance)
7
+ ## todo/fix:
8
+ ## in build_class add freeze for composite/reference objects
9
+ ## that is, arrays, hash mappings, structs etc.
10
+ ## freeze only works for now for "value" objects e.g. integer, bool, etc.
11
+ @zero ||= new_zero.freeze
12
+ end
13
+
14
+ def zero?() self == self.class.zero; end
15
+
16
+
17
+
18
+ def initialize( *args, **kwargs )
19
+ ## fix-fix-fix: first check for matching args - why? why not?
20
+ if kwargs.size > 0 ## assume kwargs init
21
+ self.class.attributes.each do |key, type|
22
+ ## note: allow unused keys (will get set to zero)
23
+ value = kwargs.has_key?( key ) ? kwargs[ key ] : type.new_zero
24
+ value = if value.is_a?( Typed )
25
+ ## fix-fix-fix - check type match here!!!
26
+ value
27
+ else
28
+ type.new( value )
29
+ end
30
+ instance_variable_set( "@#{key}", value )
31
+ end
32
+ else
33
+ self.class.attributes.zip( args ).each do |(key, type), value|
34
+ value = if value.is_a?(Typed)
35
+ ## fix-fix-fix - check type match here!!!
36
+ value
37
+ else
38
+ type.new( value )
39
+ end
40
+ instance_variable_set( "@#{key}", value )
41
+ end
42
+ end
43
+ self ## note: return reference to self for chaining method calls
44
+ end
45
+
46
+
47
+ def as_data
48
+ self.class.attributes.keys.map do |key|
49
+ ivar = instance_variable_get( "@#{key}" )
50
+ puts " @#{key}:"
51
+ pp ivar
52
+ ivar.as_data
53
+ end
54
+ end
55
+
56
+
57
+
58
+ def ==(other)
59
+ if other.is_a?( self.class )
60
+ self.class.attributes.keys.all? do |key|
61
+ __send__( key ) == other.__send__( key )
62
+ end
63
+ else
64
+ false
65
+ end
66
+ end
67
+ ## do note override eql? why? why not?
68
+ # default is object id equality???
69
+ ## alias_method :eql?, :==
70
+
71
+
72
+ end # class Struct
73
+ end # module Types