strong_json 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +68 -16
- data/lib/strong_json/error_reporter.rb +130 -0
- data/lib/strong_json/type.rb +232 -86
- data/lib/strong_json/types.rb +11 -14
- data/lib/strong_json/version.rb +1 -1
- data/lib/strong_json.rb +3 -1
- data/sig/strong_json.rbi +25 -6
- data/sig/type.rbi +55 -28
- data/spec/array_spec.rb +3 -3
- data/spec/basetype_spec.rb +16 -8
- data/spec/enum_spec.rb +87 -9
- data/spec/error_spec.rb +62 -4
- data/spec/json_spec.rb +133 -7
- data/spec/object_spec.rb +122 -57
- data/spec/optional_spec.rb +4 -1
- metadata +3 -2
data/lib/strong_json/type.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
class StrongJSON
|
2
2
|
module Type
|
3
|
-
NONE = ::Object.new
|
4
|
-
|
5
3
|
module Match
|
6
4
|
def =~(value)
|
7
5
|
coerce(value)
|
@@ -15,8 +13,23 @@ class StrongJSON
|
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
16
|
+
module WithAlias
|
17
|
+
def alias
|
18
|
+
@alias
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_alias(name)
|
22
|
+
_ = dup.tap do |copy|
|
23
|
+
copy.instance_eval do
|
24
|
+
@alias = name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
class Base
|
19
31
|
include Match
|
32
|
+
include WithAlias
|
20
33
|
|
21
34
|
# @dynamic type
|
22
35
|
attr_reader :type
|
@@ -27,8 +40,6 @@ class StrongJSON
|
|
27
40
|
|
28
41
|
def test(value)
|
29
42
|
case @type
|
30
|
-
when :ignored
|
31
|
-
true
|
32
43
|
when :any
|
33
44
|
true
|
34
45
|
when :number
|
@@ -46,13 +57,10 @@ class StrongJSON
|
|
46
57
|
end
|
47
58
|
end
|
48
59
|
|
49
|
-
def coerce(value, path:
|
50
|
-
raise
|
51
|
-
raise IllegalTypeError.new(type: self) if path == [] && @type == :ignored
|
60
|
+
def coerce(value, path: ErrorPath.root(self))
|
61
|
+
raise TypeError.new(value: value, path: path) unless test(value)
|
52
62
|
|
53
63
|
case type
|
54
|
-
when :ignored
|
55
|
-
NONE
|
56
64
|
when :symbol
|
57
65
|
value.to_sym
|
58
66
|
else
|
@@ -61,32 +69,59 @@ class StrongJSON
|
|
61
69
|
end
|
62
70
|
|
63
71
|
def to_s
|
64
|
-
@type.to_s
|
72
|
+
self.alias&.to_s || @type.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
if other.is_a?(Base)
|
77
|
+
# @type var other: Base<any>
|
78
|
+
other.type == type
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
__skip__ = begin
|
83
|
+
alias eql? ==
|
65
84
|
end
|
66
85
|
end
|
67
86
|
|
68
87
|
class Optional
|
69
88
|
include Match
|
89
|
+
include WithAlias
|
90
|
+
|
91
|
+
# @dynamic type
|
92
|
+
attr_reader :type
|
70
93
|
|
71
94
|
def initialize(type)
|
72
95
|
@type = type
|
73
96
|
end
|
74
97
|
|
75
|
-
def coerce(value, path:
|
76
|
-
unless value == nil
|
77
|
-
@type.coerce(value, path: path)
|
98
|
+
def coerce(value, path: ErrorPath.root(self))
|
99
|
+
unless value == nil
|
100
|
+
@type.coerce(value, path: path.expand(type: @type))
|
78
101
|
else
|
79
102
|
nil
|
80
103
|
end
|
81
104
|
end
|
82
105
|
|
83
106
|
def to_s
|
84
|
-
"optional(#{@type})"
|
107
|
+
self.alias&.to_s || "optional(#{@type})"
|
108
|
+
end
|
109
|
+
|
110
|
+
def ==(other)
|
111
|
+
if other.is_a?(Optional)
|
112
|
+
# @type var other: Optional<any>
|
113
|
+
other.type == type
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
__skip__ = begin
|
118
|
+
alias eql? ==
|
85
119
|
end
|
86
120
|
end
|
87
121
|
|
88
122
|
class Literal
|
89
123
|
include Match
|
124
|
+
include WithAlias
|
90
125
|
|
91
126
|
# @dynamic value
|
92
127
|
attr_reader :value
|
@@ -96,176 +131,287 @@ class StrongJSON
|
|
96
131
|
end
|
97
132
|
|
98
133
|
def to_s
|
99
|
-
|
134
|
+
self.alias&.to_s || (_ = @value).inspect
|
100
135
|
end
|
101
136
|
|
102
|
-
def coerce(value, path:
|
103
|
-
raise
|
137
|
+
def coerce(value, path: ErrorPath.root(self))
|
138
|
+
raise TypeError.new(path: path, value: value) unless (_ = self.value) == value
|
104
139
|
value
|
105
140
|
end
|
141
|
+
|
142
|
+
def ==(other)
|
143
|
+
if other.is_a?(Literal)
|
144
|
+
# @type var other: Literal<any>
|
145
|
+
other.value == value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
__skip__ = begin
|
150
|
+
alias eql? ==
|
151
|
+
end
|
106
152
|
end
|
107
153
|
|
108
154
|
class Array
|
109
155
|
include Match
|
156
|
+
include WithAlias
|
157
|
+
|
158
|
+
# @dynamic type
|
159
|
+
attr_reader :type
|
110
160
|
|
111
161
|
def initialize(type)
|
112
162
|
@type = type
|
113
163
|
end
|
114
164
|
|
115
|
-
def coerce(value, path:
|
165
|
+
def coerce(value, path: ErrorPath.root(self))
|
116
166
|
if value.is_a?(::Array)
|
117
167
|
value.map.with_index do |v, i|
|
118
|
-
@type.coerce(v, path: path
|
168
|
+
@type.coerce(v, path: path.dig(key: i, type: @type))
|
119
169
|
end
|
120
170
|
else
|
121
|
-
raise
|
171
|
+
raise TypeError.new(path: path, value: value)
|
122
172
|
end
|
123
173
|
end
|
124
174
|
|
125
175
|
def to_s
|
126
|
-
"array(#{@type})"
|
176
|
+
self.alias&.to_s || "array(#{@type})"
|
177
|
+
end
|
178
|
+
|
179
|
+
def ==(other)
|
180
|
+
if other.is_a?(Array)
|
181
|
+
# @type var other: Array<any>
|
182
|
+
other.type == type
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
__skip__ = begin
|
187
|
+
alias eql? ==
|
127
188
|
end
|
128
189
|
end
|
129
190
|
|
130
191
|
class Object
|
131
192
|
include Match
|
193
|
+
include WithAlias
|
132
194
|
|
133
|
-
|
195
|
+
# @dynamic fields, ignored_attributes, prohibited_attributes
|
196
|
+
attr_reader :fields, :ignored_attributes, :prohibited_attributes
|
197
|
+
|
198
|
+
def initialize(fields, ignored_attributes:, prohibited_attributes:)
|
134
199
|
@fields = fields
|
200
|
+
@ignored_attributes = ignored_attributes
|
201
|
+
@prohibited_attributes = prohibited_attributes
|
135
202
|
end
|
136
203
|
|
137
|
-
def coerce(object, path:
|
204
|
+
def coerce(object, path: ErrorPath.root(self))
|
138
205
|
unless object.is_a?(Hash)
|
139
|
-
raise
|
206
|
+
raise TypeError.new(path: path, value: object)
|
140
207
|
end
|
141
208
|
|
142
|
-
|
143
|
-
|
209
|
+
unless (intersection = Set.new(object.keys).intersection(prohibited_attributes)).empty?
|
210
|
+
raise UnexpectedAttributeError.new(path: path, attribute: intersection.to_a.first)
|
211
|
+
end
|
144
212
|
|
145
|
-
|
146
|
-
|
147
|
-
|
213
|
+
case attrs = ignored_attributes
|
214
|
+
when :any
|
215
|
+
object = object.dup
|
216
|
+
extra_keys = Set.new(object.keys) - Set.new(fields.keys)
|
217
|
+
extra_keys.each do |key|
|
218
|
+
object.delete(key)
|
219
|
+
end
|
220
|
+
when Set
|
221
|
+
object = object.dup
|
222
|
+
attrs.each do |key|
|
223
|
+
object.delete(key)
|
148
224
|
end
|
149
225
|
end
|
150
226
|
|
151
|
-
@
|
152
|
-
|
227
|
+
# @type var result: ::Hash<Symbol, any>
|
228
|
+
result = {}
|
153
229
|
|
154
|
-
|
155
|
-
|
230
|
+
object.each do |key, _|
|
231
|
+
unless fields.key?(key)
|
232
|
+
raise UnexpectedAttributeError.new(path: path, attribute: key)
|
156
233
|
end
|
157
234
|
end
|
158
235
|
|
236
|
+
fields.each do |key, type|
|
237
|
+
result[key] = type.coerce(object[key], path: path.dig(key: key, type: type))
|
238
|
+
end
|
239
|
+
|
159
240
|
_ = result
|
160
241
|
end
|
161
242
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
return if NONE.equal?(v) || NONE.equal?(type)
|
166
|
-
return if type.is_a?(Optional) && NONE.equal?(value)
|
167
|
-
|
168
|
-
yield(v)
|
243
|
+
def ignore(attrs)
|
244
|
+
Object.new(fields, ignored_attributes: attrs, prohibited_attributes: prohibited_attributes)
|
169
245
|
end
|
170
246
|
|
171
|
-
def
|
172
|
-
|
247
|
+
def ignore!(attrs)
|
248
|
+
@ignored_attributes = attrs
|
249
|
+
self
|
250
|
+
end
|
173
251
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
when Hash
|
178
|
-
fields
|
179
|
-
end
|
252
|
+
def prohibit(attrs)
|
253
|
+
Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: attrs)
|
254
|
+
end
|
180
255
|
|
181
|
-
|
256
|
+
def prohibit!(attrs)
|
257
|
+
@prohibited_attributes = attrs
|
258
|
+
self
|
182
259
|
end
|
183
260
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
261
|
+
def update_fields
|
262
|
+
fields.dup.yield_self do |fields|
|
263
|
+
yield fields
|
264
|
+
|
265
|
+
Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: prohibited_attributes)
|
266
|
+
end
|
188
267
|
end
|
189
268
|
|
190
269
|
def to_s
|
191
|
-
|
192
|
-
|
270
|
+
fields = @fields.map do |name, type|
|
271
|
+
"#{name}: #{type}"
|
272
|
+
end
|
193
273
|
|
194
|
-
|
195
|
-
|
274
|
+
self.alias&.to_s || "object(#{fields.join(', ')})"
|
275
|
+
end
|
276
|
+
|
277
|
+
def ==(other)
|
278
|
+
if other.is_a?(Object)
|
279
|
+
# @type var other: Object<any>
|
280
|
+
other.fields == fields &&
|
281
|
+
other.ignored_attributes == ignored_attributes &&
|
282
|
+
other.prohibited_attributes == prohibited_attributes
|
196
283
|
end
|
284
|
+
end
|
197
285
|
|
198
|
-
|
286
|
+
__skip__ = begin
|
287
|
+
alias eql? ==
|
199
288
|
end
|
200
289
|
end
|
201
290
|
|
202
291
|
class Enum
|
203
292
|
include Match
|
293
|
+
include WithAlias
|
204
294
|
|
205
|
-
# @dynamic types
|
295
|
+
# @dynamic types, detector
|
206
296
|
attr_reader :types
|
297
|
+
attr_reader :detector
|
207
298
|
|
208
|
-
def initialize(types)
|
299
|
+
def initialize(types, detector = nil)
|
209
300
|
@types = types
|
301
|
+
@detector = detector
|
210
302
|
end
|
211
303
|
|
212
304
|
def to_s
|
213
|
-
"enum(#{types.map(&:to_s).join(", ")})"
|
305
|
+
self.alias&.to_s || "enum(#{types.map(&:to_s).join(", ")})"
|
214
306
|
end
|
215
307
|
|
216
|
-
def coerce(value, path:
|
308
|
+
def coerce(value, path: ErrorPath.root(self))
|
309
|
+
if d = detector
|
310
|
+
type = d[value]
|
311
|
+
if type && types.include?(type)
|
312
|
+
return type.coerce(value, path: path.expand(type: type))
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
217
316
|
types.each do |ty|
|
218
317
|
begin
|
219
|
-
return ty.coerce(value, path: path)
|
220
|
-
rescue
|
318
|
+
return ty.coerce(value, path: path.expand(type: ty))
|
319
|
+
rescue UnexpectedAttributeError, TypeError # rubocop:disable Lint/HandleExceptions
|
221
320
|
end
|
222
321
|
end
|
223
322
|
|
224
|
-
raise
|
323
|
+
raise TypeError.new(path: path, value: value)
|
324
|
+
end
|
325
|
+
|
326
|
+
def ==(other)
|
327
|
+
if other.is_a?(Enum)
|
328
|
+
# @type var other: Enum<any>
|
329
|
+
other.types == types &&
|
330
|
+
other.detector == detector
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
__skip__ = begin
|
335
|
+
alias eql? ==
|
225
336
|
end
|
226
337
|
end
|
227
338
|
|
228
|
-
class
|
339
|
+
class UnexpectedAttributeError < StandardError
|
340
|
+
# @dynamic path, attribute
|
341
|
+
attr_reader :path, :attribute
|
342
|
+
|
343
|
+
def initialize(path:, attribute:)
|
344
|
+
@path = path
|
345
|
+
@attribute = attribute
|
346
|
+
super "UnexpectedAttributeError at #{path.to_s}: attribute=#{attribute}"
|
347
|
+
end
|
348
|
+
|
349
|
+
def type
|
350
|
+
path.type
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class TypeError < StandardError
|
229
355
|
# @dynamic path, value
|
230
356
|
attr_reader :path, :value
|
231
357
|
|
232
|
-
def initialize(path
|
358
|
+
def initialize(path:, value:)
|
233
359
|
@path = path
|
234
360
|
@value = value
|
361
|
+
type = path.type
|
362
|
+
s = type.alias || type
|
363
|
+
super "TypeError at #{path.to_s}: expected=#{s}, value=#{value.inspect}"
|
235
364
|
end
|
236
365
|
|
237
|
-
def
|
238
|
-
|
239
|
-
"Unexpected field of #{position} (#{value})"
|
366
|
+
def type
|
367
|
+
path.type
|
240
368
|
end
|
241
369
|
end
|
242
370
|
|
243
|
-
class
|
244
|
-
# @dynamic type
|
245
|
-
attr_reader :type
|
371
|
+
class ErrorPath
|
372
|
+
# @dynamic type, parent
|
373
|
+
attr_reader :type, :parent
|
246
374
|
|
247
|
-
def initialize(type:)
|
375
|
+
def initialize(type:, parent:)
|
248
376
|
@type = type
|
377
|
+
@parent = parent
|
249
378
|
end
|
250
379
|
|
251
|
-
def
|
252
|
-
|
380
|
+
def dig(key:, type:)
|
381
|
+
# @type var parent: [Integer | Symbol | nil, ErrorPath]
|
382
|
+
parent = [key, self]
|
383
|
+
self.class.new(type: type, parent: parent)
|
253
384
|
end
|
254
|
-
end
|
255
385
|
|
256
|
-
|
257
|
-
|
258
|
-
|
386
|
+
def expand(type:)
|
387
|
+
# @type var parent: [Integer | Symbol | nil, ErrorPath]
|
388
|
+
parent = [nil, self]
|
389
|
+
self.class.new(type: type, parent: parent)
|
390
|
+
end
|
259
391
|
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
392
|
+
def self.root(type)
|
393
|
+
self.new(type: type, parent: nil)
|
394
|
+
end
|
395
|
+
|
396
|
+
def root?
|
397
|
+
!parent
|
264
398
|
end
|
265
399
|
|
266
400
|
def to_s
|
267
|
-
|
268
|
-
|
401
|
+
if pa = parent
|
402
|
+
if key = pa[0]
|
403
|
+
pa[1].to_s + case key
|
404
|
+
when Integer
|
405
|
+
"[#{key}]"
|
406
|
+
when Symbol
|
407
|
+
".#{key}"
|
408
|
+
end
|
409
|
+
else
|
410
|
+
pa[1].to_s
|
411
|
+
end
|
412
|
+
else
|
413
|
+
"$"
|
414
|
+
end
|
269
415
|
end
|
270
416
|
end
|
271
417
|
end
|
data/lib/strong_json/types.rb
CHANGED
@@ -2,7 +2,11 @@ class StrongJSON
|
|
2
2
|
module Types
|
3
3
|
# @type method object: (?Hash<Symbol, ty>) -> Type::Object<any>
|
4
4
|
def object(fields = {})
|
5
|
-
|
5
|
+
if fields.empty?
|
6
|
+
Type::Object.new(fields, ignored_attributes: nil, prohibited_attributes: Set.new)
|
7
|
+
else
|
8
|
+
Type::Object.new(fields, ignored_attributes: :any, prohibited_attributes: Set.new)
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
# @type method array: (?ty) -> Type::Array<any>
|
@@ -39,10 +43,6 @@ class StrongJSON
|
|
39
43
|
optional(any)
|
40
44
|
end
|
41
45
|
|
42
|
-
def prohibited
|
43
|
-
StrongJSON::Type::Base.new(:prohibited)
|
44
|
-
end
|
45
|
-
|
46
46
|
def symbol
|
47
47
|
StrongJSON::Type::Base.new(:symbol)
|
48
48
|
end
|
@@ -51,8 +51,8 @@ class StrongJSON
|
|
51
51
|
StrongJSON::Type::Literal.new(value)
|
52
52
|
end
|
53
53
|
|
54
|
-
def enum(*types)
|
55
|
-
StrongJSON::Type::Enum.new(types)
|
54
|
+
def enum(*types, detector: nil)
|
55
|
+
StrongJSON::Type::Enum.new(types, detector)
|
56
56
|
end
|
57
57
|
|
58
58
|
def string?
|
@@ -75,15 +75,12 @@ class StrongJSON
|
|
75
75
|
optional(symbol)
|
76
76
|
end
|
77
77
|
|
78
|
-
def ignored
|
79
|
-
StrongJSON::Type::Base.new(:ignored)
|
80
|
-
end
|
81
|
-
|
82
78
|
def array?(ty)
|
83
79
|
optional(array(ty))
|
84
80
|
end
|
85
81
|
|
86
|
-
|
82
|
+
# @type method object?: (?Hash<Symbol, ty>) -> Type::Optional<any>
|
83
|
+
def object?(fields={})
|
87
84
|
optional(object(fields))
|
88
85
|
end
|
89
86
|
|
@@ -91,8 +88,8 @@ class StrongJSON
|
|
91
88
|
optional(literal(value))
|
92
89
|
end
|
93
90
|
|
94
|
-
def enum?(*types)
|
95
|
-
optional(enum(*types))
|
91
|
+
def enum?(*types, detector: nil)
|
92
|
+
optional(enum(*types, detector: detector))
|
96
93
|
end
|
97
94
|
end
|
98
95
|
end
|
data/lib/strong_json/version.rb
CHANGED
data/lib/strong_json.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "strong_json/version"
|
2
2
|
require "strong_json/type"
|
3
3
|
require "strong_json/types"
|
4
|
+
require "strong_json/error_reporter"
|
5
|
+
require "prettyprint"
|
4
6
|
|
5
7
|
class StrongJSON
|
6
8
|
def initialize(&block)
|
@@ -8,7 +10,7 @@ class StrongJSON
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def let(name, type)
|
11
|
-
define_singleton_method(name) { type }
|
13
|
+
define_singleton_method(name) { type.with_alias(name) }
|
12
14
|
end
|
13
15
|
|
14
16
|
include StrongJSON::Types
|
data/sig/strong_json.rbi
CHANGED
@@ -6,20 +6,30 @@ end
|
|
6
6
|
|
7
7
|
StrongJSON::VERSION: String
|
8
8
|
|
9
|
+
class StandardError
|
10
|
+
def initialize: (String) -> any
|
11
|
+
end
|
12
|
+
|
9
13
|
interface StrongJSON::_Schema<'type>
|
10
|
-
def coerce: (any, ?path: ::
|
14
|
+
def coerce: (any, ?path: Type::ErrorPath) -> 'type
|
11
15
|
def =~: (any) -> bool
|
12
16
|
def to_s: -> String
|
13
17
|
def is_a?: (any) -> bool
|
18
|
+
def alias: -> Symbol?
|
19
|
+
def with_alias: (Symbol) -> self
|
20
|
+
def ==: (any) -> bool
|
21
|
+
def yield_self: <'a> () { (self) -> 'a } -> 'a
|
14
22
|
end
|
15
23
|
|
16
24
|
type StrongJSON::ty = _Schema<any>
|
17
25
|
|
18
26
|
module StrongJSON::Types
|
19
27
|
def object: <'x> (Hash<Symbol, ty>) -> Type::Object<'x>
|
20
|
-
| () -> Type::Object<
|
28
|
+
| () -> Type::Object<{}>
|
21
29
|
def object?: <'x> (Hash<Symbol, ty>) -> Type::Optional<'x>
|
30
|
+
| () -> Type::Optional<{}>
|
22
31
|
def any: () -> Type::Base<any>
|
32
|
+
def any?: () -> Type::Optional<any>
|
23
33
|
def optional: <'x> (_Schema<'x>) -> Type::Optional<'x>
|
24
34
|
| () -> Type::Optional<any>
|
25
35
|
def string: () -> Type::Base<String>
|
@@ -37,8 +47,17 @@ module StrongJSON::Types
|
|
37
47
|
def array?: <'x> (_Schema<'x>) -> Type::Optional<::Array<'x>>
|
38
48
|
def literal: <'x> ('x) -> Type::Literal<'x>
|
39
49
|
def literal?: <'x> ('x) -> Type::Optional<'x>
|
40
|
-
def enum: <'x> (*_Schema<any
|
41
|
-
def enum?: <'x> (*_Schema<any
|
42
|
-
|
43
|
-
|
50
|
+
def enum: <'x> (*_Schema<any>, ?detector: Type::detector?) -> Type::Enum<'x>
|
51
|
+
def enum?: <'x> (*_Schema<any>, ?detector: Type::detector?) -> Type::Optional<'x>
|
52
|
+
end
|
53
|
+
|
54
|
+
class StrongJSON::ErrorReporter
|
55
|
+
attr_reader path: Type::ErrorPath
|
56
|
+
@string: String
|
57
|
+
def initialize: (path: Type::ErrorPath) -> any
|
58
|
+
def format: -> void
|
59
|
+
def (private) format_trace: (path: Type::ErrorPath, ?index: Integer) -> void
|
60
|
+
def (private) format_aliases: (path: Type::ErrorPath, where: ::Array<String>) -> ::Array<String>
|
61
|
+
def (private) pretty: (ty, any, ?expand_alias: bool) -> void
|
62
|
+
def pretty_str: (ty, ?expand_alias: bool) -> ::String
|
44
63
|
end
|