dry-types 0.15.0 → 1.5.1

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