dry-types 0.15.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +547 -161
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +27 -30
  6. data/lib/dry/types/any.rb +23 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +74 -15
  9. data/lib/dry/types/array.rb +18 -2
  10. data/lib/dry/types/builder.rb +118 -22
  11. data/lib/dry/types/builder_methods.rb +46 -16
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +117 -32
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compiler.rb +44 -21
  16. data/lib/dry/types/constrained/coercible.rb +36 -6
  17. data/lib/dry/types/constrained.rb +79 -31
  18. data/lib/dry/types/constraints.rb +18 -4
  19. data/lib/dry/types/constructor/function.rb +216 -0
  20. data/lib/dry/types/constructor/wrapper.rb +94 -0
  21. data/lib/dry/types/constructor.rb +110 -61
  22. data/lib/dry/types/container.rb +6 -1
  23. data/lib/dry/types/core.rb +34 -11
  24. data/lib/dry/types/decorator.rb +38 -17
  25. data/lib/dry/types/default.rb +61 -16
  26. data/lib/dry/types/enum.rb +36 -20
  27. data/lib/dry/types/errors.rb +74 -8
  28. data/lib/dry/types/extensions/maybe.rb +65 -17
  29. data/lib/dry/types/extensions/monads.rb +29 -0
  30. data/lib/dry/types/extensions.rb +7 -1
  31. data/lib/dry/types/fn_container.rb +6 -1
  32. data/lib/dry/types/hash/constructor.rb +17 -4
  33. data/lib/dry/types/hash.rb +32 -20
  34. data/lib/dry/types/inflector.rb +3 -1
  35. data/lib/dry/types/json.rb +18 -16
  36. data/lib/dry/types/lax.rb +75 -0
  37. data/lib/dry/types/map.rb +70 -32
  38. data/lib/dry/types/meta.rb +51 -0
  39. data/lib/dry/types/module.rb +16 -11
  40. data/lib/dry/types/nominal.rb +113 -22
  41. data/lib/dry/types/options.rb +12 -25
  42. data/lib/dry/types/params.rb +39 -25
  43. data/lib/dry/types/predicate_inferrer.rb +238 -0
  44. data/lib/dry/types/predicate_registry.rb +34 -0
  45. data/lib/dry/types/primitive_inferrer.rb +97 -0
  46. data/lib/dry/types/printable.rb +5 -1
  47. data/lib/dry/types/printer.rb +63 -57
  48. data/lib/dry/types/result.rb +29 -3
  49. data/lib/dry/types/schema/key.rb +62 -36
  50. data/lib/dry/types/schema.rb +201 -91
  51. data/lib/dry/types/spec/types.rb +99 -37
  52. data/lib/dry/types/sum.rb +75 -25
  53. data/lib/dry/types/type.rb +49 -0
  54. data/lib/dry/types/version.rb +3 -1
  55. data/lib/dry/types.rb +106 -48
  56. data/lib/dry-types.rb +3 -1
  57. metadata +55 -78
  58. data/.codeclimate.yml +0 -15
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.rubocop.yml +0 -43
  62. data/.travis.yml +0 -28
  63. data/.yardopts +0 -5
  64. data/CONTRIBUTING.md +0 -29
  65. data/Gemfile +0 -23
  66. data/Rakefile +0 -20
  67. data/benchmarks/hash_schemas.rb +0 -51
  68. data/lib/dry/types/safe.rb +0 -61
  69. data/log/.gitkeep +0 -0
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
5
+ # Common API for building type objects in a convenient way
6
+ #
7
+ #
8
+ # @api public
3
9
  module BuilderMethods
4
10
  # @api private
5
11
  def included(base)
@@ -8,7 +14,8 @@ module Dry
8
14
  end
9
15
 
10
16
  # Build an array type.
11
- # It is a shortcut for Array.of
17
+ #
18
+ # Shortcut for Array#of.
12
19
  #
13
20
  # @example
14
21
  # Types::Strings = Types.Array(Types::String)
@@ -17,7 +24,7 @@ module Dry
17
24
  #
18
25
  # @return [Dry::Types::Array]
19
26
  def Array(type)
20
- self::Array.of(type)
27
+ Strict(::Array).of(type)
21
28
  end
22
29
 
23
30
  # Build a hash schema
@@ -25,9 +32,8 @@ module Dry
25
32
  # @param [Hash{Symbol => Dry::Types::Type}] type_map
26
33
  #
27
34
  # @return [Dry::Types::Array]
28
- # @api public
29
35
  def Hash(type_map)
30
- self::Hash.schema(type_map)
36
+ Strict(::Hash).schema(type_map)
31
37
  end
32
38
 
33
39
  # Build a type which values are instances of a given class
@@ -41,9 +47,8 @@ module Dry
41
47
  # @param [Class,Module] klass Class or module
42
48
  #
43
49
  # @return [Dry::Types::Type]
44
- # @api public
45
50
  def Instance(klass)
46
- Nominal.new(klass).constrained(type: klass)
51
+ Nominal(klass).constrained(type: klass)
47
52
  end
48
53
  alias_method :Strict, :Instance
49
54
 
@@ -53,9 +58,8 @@ module Dry
53
58
  # @param [Object] value
54
59
  #
55
60
  # @return [Dry::Types::Type]
56
- # @api public
57
61
  def Value(value)
58
- Nominal.new(value.class).constrained(eql: value)
62
+ Nominal(value.class).constrained(eql: value)
59
63
  end
60
64
 
61
65
  # Build a type with a single value
@@ -64,9 +68,8 @@ module Dry
64
68
  # @param [Object] object
65
69
  #
66
70
  # @return [Dry::Types::Type]
67
- # @api public
68
71
  def Constant(object)
69
- Nominal.new(object.class).constrained(is: object)
72
+ Nominal(object.class).constrained(is: object)
70
73
  end
71
74
 
72
75
  # Build a constructor type
@@ -77,9 +80,16 @@ module Dry
77
80
  # @param [#call,nil] block Value constructor
78
81
  #
79
82
  # @return [Dry::Types::Type]
80
- # @api public
81
83
  def Constructor(klass, cons = nil, &block)
82
- Nominal.new(klass).constructor(cons || block || klass.method(:new))
84
+ if klass.is_a?(Type)
85
+ if cons || block
86
+ klass.constructor(cons || block)
87
+ else
88
+ klass
89
+ end
90
+ else
91
+ Nominal(klass).constructor(cons || block || klass.method(:new))
92
+ end
83
93
  end
84
94
 
85
95
  # Build a nominal type
@@ -87,9 +97,14 @@ module Dry
87
97
  # @param [Class] klass
88
98
  #
89
99
  # @return [Dry::Types::Type]
90
- # @api public
91
100
  def Nominal(klass)
92
- Nominal.new(klass)
101
+ if klass <= ::Array
102
+ Array.new(klass)
103
+ elsif klass <= ::Hash
104
+ Hash.new(klass)
105
+ else
106
+ Nominal.new(klass)
107
+ end
93
108
  end
94
109
 
95
110
  # Build a map type
@@ -102,9 +117,24 @@ module Dry
102
117
  # @param [Type] value_type Value type
103
118
  #
104
119
  # @return [Dry::Types::Map]
105
- # @api public
106
120
  def Map(key_type, value_type)
107
- Types['nominal.hash'].map(key_type, value_type)
121
+ Nominal(::Hash).map(key_type, value_type)
122
+ end
123
+
124
+ # Builds a constrained nominal type accepting any value that
125
+ # responds to given methods
126
+ #
127
+ # @example
128
+ # Types::Callable = Types.Interface(:call)
129
+ # Types::Contact = Types.Interface(:name, :address)
130
+ #
131
+ # @param methods [Array<String, Symbol>] Method names
132
+ #
133
+ # @return [Dry::Types::Contrained]
134
+ def Interface(*methods)
135
+ methods.reduce(Types["nominal.any"]) do |type, method|
136
+ type.constrained(respond_to: method)
137
+ end
108
138
  end
109
139
  end
110
140
  end
@@ -1,19 +1,55 @@
1
- require 'date'
2
- require 'bigdecimal'
3
- require 'bigdecimal/util'
4
- require 'time'
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "bigdecimal"
5
+ require "bigdecimal/util"
6
+ require "time"
5
7
 
6
8
  module Dry
7
9
  module Types
8
10
  module Coercions
11
+ # JSON-specific coercions
12
+ #
13
+ # @api public
9
14
  module JSON
10
15
  extend Coercions
11
16
 
17
+ # @param [Object] input
18
+ #
19
+ # @return [nil] if the input is nil
20
+ #
21
+ # @raise CoercionError
22
+ #
23
+ # @api public
24
+ def self.to_nil(input, &_block)
25
+ if input.nil?
26
+ nil
27
+ elsif block_given?
28
+ yield
29
+ else
30
+ raise CoercionError, "#{input.inspect} is not nil"
31
+ end
32
+ end
33
+
12
34
  # @param [#to_d, Object] input
35
+ #
13
36
  # @return [BigDecimal,nil]
14
- def self.to_decimal(input)
15
- return if input.nil?
16
- input.to_d unless empty_str?(input)
37
+ #
38
+ # @raise CoercionError
39
+ #
40
+ # @api public
41
+ def self.to_decimal(input, &_block)
42
+ if input.is_a?(::Float)
43
+ input.to_d
44
+ else
45
+ BigDecimal(input)
46
+ end
47
+ rescue ArgumentError, TypeError
48
+ if block_given?
49
+ yield
50
+ else
51
+ raise CoercionError, "#{input} cannot be coerced to decimal"
52
+ end
17
53
  end
18
54
  end
19
55
  end
@@ -1,80 +1,165 @@
1
- require 'bigdecimal'
2
- require 'bigdecimal/util'
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "bigdecimal/util"
3
5
 
4
6
  module Dry
5
7
  module Types
6
8
  module Coercions
9
+ # Params-specific coercions
10
+ #
11
+ # @api public
7
12
  module Params
8
13
  TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
9
14
  FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
10
- BOOLEAN_MAP = ::Hash[TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])].freeze
15
+ BOOLEAN_MAP = ::Hash[
16
+ TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])
17
+ ].merge(true => true, false => false).freeze
11
18
 
12
19
  extend Coercions
13
20
 
21
+ # @param [Object] input
22
+ #
23
+ # @return [nil] if the input is an empty string or nil
24
+ #
25
+ # @raise CoercionError
26
+ #
27
+ # @api public
28
+ def self.to_nil(input, &_block)
29
+ if input.nil? || empty_str?(input)
30
+ nil
31
+ elsif block_given?
32
+ yield
33
+ else
34
+ raise CoercionError, "#{input.inspect} is not nil"
35
+ end
36
+ end
37
+
14
38
  # @param [String, Object] input
39
+ #
15
40
  # @return [Boolean,Object]
41
+ #
16
42
  # @see TRUE_VALUES
17
43
  # @see FALSE_VALUES
18
- def self.to_true(input)
19
- BOOLEAN_MAP.fetch(input.to_s, input)
44
+ #
45
+ # @raise CoercionError
46
+ #
47
+ # @api public
48
+ def self.to_true(input, &_block)
49
+ BOOLEAN_MAP.fetch(input.to_s) do
50
+ if block_given?
51
+ yield
52
+ else
53
+ raise CoercionError, "#{input} cannot be coerced to true"
54
+ end
55
+ end
20
56
  end
21
57
 
22
58
  # @param [String, Object] input
59
+ #
23
60
  # @return [Boolean,Object]
61
+ #
24
62
  # @see TRUE_VALUES
25
63
  # @see FALSE_VALUES
26
- def self.to_false(input)
27
- BOOLEAN_MAP.fetch(input.to_s, input)
64
+ #
65
+ # @raise CoercionError
66
+ #
67
+ # @api public
68
+ def self.to_false(input, &_block)
69
+ BOOLEAN_MAP.fetch(input.to_s) do
70
+ if block_given?
71
+ yield
72
+ else
73
+ raise CoercionError, "#{input} cannot be coerced to false"
74
+ end
75
+ end
28
76
  end
29
77
 
30
78
  # @param [#to_int, #to_i, Object] input
79
+ #
31
80
  # @return [Integer, nil, Object]
32
- def self.to_int(input)
33
- if empty_str?(input)
34
- nil
35
- elsif input.is_a? String
81
+ #
82
+ # @raise CoercionError
83
+ #
84
+ # @api public
85
+ def self.to_int(input, &block)
86
+ if input.is_a? String
36
87
  Integer(input, 10)
37
88
  else
38
89
  Integer(input)
39
90
  end
40
- rescue ArgumentError, TypeError
41
- input
91
+ rescue ArgumentError, TypeError => e
92
+ CoercionError.handle(e, &block)
42
93
  end
43
94
 
44
95
  # @param [#to_f, Object] input
96
+ #
45
97
  # @return [Float, nil, Object]
46
- def self.to_float(input)
47
- if empty_str?(input)
48
- nil
49
- else
50
- Float(input)
51
- end
52
- rescue ArgumentError, TypeError
53
- input
98
+ #
99
+ # @raise CoercionError
100
+ #
101
+ # @api public
102
+ def self.to_float(input, &block)
103
+ Float(input)
104
+ rescue ArgumentError, TypeError => e
105
+ CoercionError.handle(e, &block)
54
106
  end
55
107
 
56
108
  # @param [#to_d, Object] input
109
+ #
57
110
  # @return [BigDecimal, nil, Object]
58
- def self.to_decimal(input)
59
- result = to_float(input)
60
-
61
- if result.instance_of?(Float)
62
- input.to_d
63
- else
64
- result
111
+ #
112
+ # @raise CoercionError
113
+ #
114
+ # @api public
115
+ def self.to_decimal(input, &_block)
116
+ to_float(input) do
117
+ if block_given?
118
+ return yield
119
+ else
120
+ raise CoercionError, "#{input.inspect} cannot be coerced to decimal"
121
+ end
65
122
  end
123
+
124
+ input.to_d
66
125
  end
67
126
 
68
127
  # @param [Array, String, Object] input
128
+ #
69
129
  # @return [Array, Object]
70
- def self.to_ary(input)
71
- empty_str?(input) ? [] : input
130
+ #
131
+ # @raise CoercionError
132
+ #
133
+ # @api public
134
+ def self.to_ary(input, &_block)
135
+ if empty_str?(input)
136
+ []
137
+ elsif input.is_a?(::Array)
138
+ input
139
+ elsif block_given?
140
+ yield
141
+ else
142
+ raise CoercionError, "#{input.inspect} cannot be coerced to array"
143
+ end
72
144
  end
73
145
 
74
146
  # @param [Hash, String, Object] input
147
+ #
75
148
  # @return [Hash, Object]
76
- def self.to_hash(input)
77
- empty_str?(input) ? {} : input
149
+ #
150
+ # @raise CoercionError
151
+ #
152
+ # @api public
153
+ def self.to_hash(input, &_block)
154
+ if empty_str?(input)
155
+ {}
156
+ elsif input.is_a?(::Hash)
157
+ input
158
+ elsif block_given?
159
+ yield
160
+ else
161
+ raise CoercionError, "#{input.inspect} cannot be coerced to hash"
162
+ end
78
163
  end
79
164
  end
80
165
  end
@@ -1,50 +1,104 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
5
+ # Common coercion functions used by the built-in `Params` and `JSON` types
6
+ #
7
+ # @api public
3
8
  module Coercions
4
9
  include Dry::Core::Constants
5
10
 
6
- # @param [String, Object] input
7
- # @return [nil] if the input is an empty string
8
- # @return [Object] otherwise the input object is returned
9
- def to_nil(input)
10
- input unless empty_str?(input)
11
- end
12
-
13
11
  # @param [#to_str, Object] input
12
+ #
14
13
  # @return [Date, Object]
14
+ #
15
15
  # @see Date.parse
16
- def to_date(input)
17
- return input unless input.respond_to?(:to_str)
18
- Date.parse(input)
19
- rescue ArgumentError, RangeError
20
- input
16
+ #
17
+ # @api public
18
+ def to_date(input, &block)
19
+ if input.respond_to?(:to_str)
20
+ begin
21
+ ::Date.parse(input)
22
+ rescue ArgumentError, RangeError => e
23
+ CoercionError.handle(e, &block)
24
+ end
25
+ elsif input.is_a?(::Date)
26
+ input
27
+ elsif block_given?
28
+ yield
29
+ else
30
+ raise CoercionError, "#{input.inspect} is not a string"
31
+ end
21
32
  end
22
33
 
23
34
  # @param [#to_str, Object] input
35
+ #
24
36
  # @return [DateTime, Object]
37
+ #
25
38
  # @see DateTime.parse
26
- def to_date_time(input)
27
- return input unless input.respond_to?(:to_str)
28
- DateTime.parse(input)
29
- rescue ArgumentError
30
- input
39
+ #
40
+ # @api public
41
+ def to_date_time(input, &block)
42
+ if input.respond_to?(:to_str)
43
+ begin
44
+ ::DateTime.parse(input)
45
+ rescue ArgumentError => e
46
+ CoercionError.handle(e, &block)
47
+ end
48
+ elsif input.is_a?(::DateTime)
49
+ input
50
+ elsif block_given?
51
+ yield
52
+ else
53
+ raise CoercionError, "#{input.inspect} is not a string"
54
+ end
31
55
  end
32
56
 
33
57
  # @param [#to_str, Object] input
58
+ #
34
59
  # @return [Time, Object]
60
+ #
35
61
  # @see Time.parse
36
- def to_time(input)
37
- return input unless input.respond_to?(:to_str)
38
- Time.parse(input)
39
- rescue ArgumentError
40
- input
62
+ #
63
+ # @api public
64
+ def to_time(input, &block)
65
+ if input.respond_to?(:to_str)
66
+ begin
67
+ ::Time.parse(input)
68
+ rescue ArgumentError => e
69
+ CoercionError.handle(e, &block)
70
+ end
71
+ elsif input.is_a?(::Time)
72
+ input
73
+ elsif block_given?
74
+ yield
75
+ else
76
+ raise CoercionError, "#{input.inspect} is not a string"
77
+ end
78
+ end
79
+
80
+ # @param [#to_sym, Object] input
81
+ #
82
+ # @return [Symbol, Object]
83
+ #
84
+ # @raise CoercionError
85
+ #
86
+ # @api public
87
+ def to_symbol(input, &block)
88
+ input.to_sym
89
+ rescue NoMethodError => e
90
+ CoercionError.handle(e, &block)
41
91
  end
42
92
 
43
93
  private
44
94
 
45
95
  # Checks whether String is empty
96
+ #
46
97
  # @param [String, Object] value
98
+ #
47
99
  # @return [Boolean]
100
+ #
101
+ # @api private
48
102
  def empty_str?(value)
49
103
  EMPTY_STRING.eql?(value)
50
104
  end
@@ -1,6 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/deprecations"
4
+
1
5
  module Dry
2
6
  module Types
7
+ # @api private
3
8
  class Compiler
9
+ extend ::Dry::Core::Deprecations[:'dry-types']
10
+
4
11
  attr_reader :registry
5
12
 
6
13
  def initialize(registry)
@@ -13,29 +20,29 @@ module Dry
13
20
 
14
21
  def visit(node)
15
22
  type, body = node
16
- send(:"visit_#{ type }", body)
23
+ send(:"visit_#{type}", body)
17
24
  end
18
25
 
19
26
  def visit_constrained(node)
20
- nominal, rule, meta = node
21
- Types::Constrained.new(visit(nominal), rule: visit_rule(rule)).meta(meta)
27
+ nominal, rule = node
28
+ type = visit(nominal)
29
+ type.constrained_type.new(type, rule: visit_rule(rule))
22
30
  end
23
31
 
24
32
  def visit_constructor(node)
25
- nominal, fn_register_name, meta = node
26
- fn = Dry::Types::FnContainer[fn_register_name]
33
+ nominal, fn = node
27
34
  primitive = visit(nominal)
28
- Types::Constructor.new(primitive, meta: meta, fn: fn)
35
+ primitive.constructor(compile_fn(fn))
29
36
  end
30
37
 
31
- def visit_safe(node)
32
- ast, meta = node
33
- Types::Safe.new(visit(ast), meta: meta)
38
+ def visit_lax(node)
39
+ Types::Lax.new(visit(node))
34
40
  end
41
+ deprecate(:visit_safe, :visit_lax)
35
42
 
36
43
  def visit_nominal(node)
37
44
  type, meta = node
38
- nominal_name = "nominal.#{ Types.identifier(type) }"
45
+ nominal_name = "nominal.#{Types.identifier(type)}"
39
46
 
40
47
  if registry.registered?(nominal_name)
41
48
  registry[nominal_name].meta(meta)
@@ -56,37 +63,37 @@ module Dry
56
63
  def visit_array(node)
57
64
  member, meta = node
58
65
  member = member.is_a?(Class) ? member : visit(member)
59
- registry['nominal.array'].of(member).meta(meta)
66
+ registry["nominal.array"].of(member).meta(meta)
60
67
  end
61
68
 
62
69
  def visit_hash(node)
63
70
  opts, meta = node
64
- registry['nominal.hash'].with(opts.merge(meta: meta))
71
+ registry["nominal.hash"].with(**opts, meta: meta)
65
72
  end
66
73
 
67
74
  def visit_schema(node)
68
75
  keys, options, meta = node
69
- registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(options.merge(meta: meta))
76
+ registry["nominal.hash"].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
70
77
  end
71
78
 
72
79
  def visit_json_hash(node)
73
80
  keys, meta = node
74
- registry['json.hash'].schema(keys.map { |key| visit(key) }, meta)
81
+ registry["json.hash"].schema(keys.map { |key| visit(key) }, meta)
75
82
  end
76
83
 
77
84
  def visit_json_array(node)
78
85
  member, meta = node
79
- registry['json.array'].of(visit(member)).meta(meta)
86
+ registry["json.array"].of(visit(member)).meta(meta)
80
87
  end
81
88
 
82
89
  def visit_params_hash(node)
83
90
  keys, meta = node
84
- registry['params.hash'].schema(keys.map { |key| visit(key) }, meta)
91
+ registry["params.hash"].schema(keys.map { |key| visit(key) }, meta)
85
92
  end
86
93
 
87
94
  def visit_params_array(node)
88
95
  member, meta = node
89
- registry['params.array'].of(visit(member)).meta(meta)
96
+ registry["params.array"].of(visit(member)).meta(meta)
90
97
  end
91
98
 
92
99
  def visit_key(node)
@@ -95,17 +102,33 @@ module Dry
95
102
  end
96
103
 
97
104
  def visit_enum(node)
98
- type, mapping, meta = node
99
- Enum.new(visit(type), mapping: mapping, meta: meta)
105
+ type, mapping = node
106
+ Enum.new(visit(type), mapping: mapping)
100
107
  end
101
108
 
102
109
  def visit_map(node)
103
110
  key_type, value_type, meta = node
104
- registry['nominal.hash'].map(visit(key_type), visit(value_type)).meta(meta)
111
+ registry["nominal.hash"].map(visit(key_type), visit(value_type)).meta(meta)
105
112
  end
106
113
 
107
114
  def visit_any(meta)
108
- registry['any'].meta(meta)
115
+ registry["any"].meta(meta)
116
+ end
117
+
118
+ def compile_fn(fn)
119
+ type, *node = fn
120
+
121
+ case type
122
+ when :id
123
+ Dry::Types::FnContainer[node.fetch(0)]
124
+ when :callable
125
+ node.fetch(0)
126
+ when :method
127
+ target, method = node
128
+ target.method(method)
129
+ else
130
+ raise ArgumentError, "Cannot build callable from #{fn.inspect}"
131
+ end
109
132
  end
110
133
  end
111
134
  end