dry-types 1.5.1 → 1.7.2

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/LICENSE +1 -1
  4. data/README.md +7 -13
  5. data/dry-types.gemspec +19 -18
  6. data/lib/dry/types/array/constructor.rb +0 -2
  7. data/lib/dry/types/array/member.rb +1 -3
  8. data/lib/dry/types/array.rb +0 -3
  9. data/lib/dry/types/builder.rb +43 -14
  10. data/lib/dry/types/builder_methods.rb +1 -1
  11. data/lib/dry/types/coercions/params.rb +4 -3
  12. data/lib/dry/types/compat.rb +1 -0
  13. data/lib/dry/types/compiler.rb +1 -3
  14. data/lib/dry/types/composition.rb +152 -0
  15. data/lib/dry/types/constrained.rb +0 -5
  16. data/lib/dry/types/constraints.rb +3 -7
  17. data/lib/dry/types/constructor/function.rb +8 -9
  18. data/lib/dry/types/constructor.rb +4 -10
  19. data/lib/dry/types/container.rb +1 -3
  20. data/lib/dry/types/core.rb +2 -3
  21. data/lib/dry/types/decorator.rb +0 -2
  22. data/lib/dry/types/default.rb +3 -6
  23. data/lib/dry/types/enum.rb +0 -3
  24. data/lib/dry/types/errors.rb +13 -1
  25. data/lib/dry/types/extensions/maybe.rb +8 -5
  26. data/lib/dry/types/extensions/monads.rb +7 -2
  27. data/lib/dry/types/fn_container.rb +0 -2
  28. data/lib/dry/types/hash/constructor.rb +2 -4
  29. data/lib/dry/types/hash.rb +1 -6
  30. data/lib/dry/types/implication.rb +66 -0
  31. data/lib/dry/types/intersection.rb +108 -0
  32. data/lib/dry/types/lax.rb +1 -4
  33. data/lib/dry/types/map.rb +9 -3
  34. data/lib/dry/types/module.rb +18 -9
  35. data/lib/dry/types/nominal.rb +2 -13
  36. data/lib/dry/types/predicate_inferrer.rb +8 -9
  37. data/lib/dry/types/predicate_registry.rb +7 -7
  38. data/lib/dry/types/primitive_inferrer.rb +0 -2
  39. data/lib/dry/types/printer/composition.rb +44 -0
  40. data/lib/dry/types/printer.rb +116 -131
  41. data/lib/dry/types/result.rb +0 -2
  42. data/lib/dry/types/schema/key.rb +1 -4
  43. data/lib/dry/types/schema.rb +6 -4
  44. data/lib/dry/types/sum.rb +3 -95
  45. data/lib/dry/types/type.rb +1 -3
  46. data/lib/dry/types/version.rb +1 -1
  47. data/lib/dry/types.rb +49 -22
  48. metadata +32 -46
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types/options"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Common API for types
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require "dry/types/decorator"
5
-
6
3
  module Dry
7
4
  module Types
8
5
  # Default types are useful when a missing value should be replaced by a default one
@@ -53,7 +50,7 @@ module Dry
53
50
  # @param [Object] value
54
51
  #
55
52
  # @api private
56
- def initialize(type, value, **options)
53
+ def initialize(type, value, **)
57
54
  super
58
55
  @value = value
59
56
  end
@@ -65,8 +62,8 @@ module Dry
65
62
  # @return [Default]
66
63
  #
67
64
  # @api public
68
- def constrained(*args)
69
- type.constrained(*args).default(value)
65
+ def constrained(...)
66
+ type.constrained(...).default(value)
70
67
  end
71
68
 
72
69
  # @return [true]
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require "dry/types/decorator"
5
-
6
3
  module Dry
7
4
  module Types
8
5
  # Enum types can be used to define an enum on top of an existing type
@@ -51,6 +51,7 @@ module Dry
51
51
 
52
52
  # @param [Array<CoercionError>] errors
53
53
  def initialize(errors)
54
+ super("")
54
55
  @errors = errors
55
56
  end
56
57
 
@@ -66,11 +67,22 @@ module Dry
66
67
  end
67
68
 
68
69
  class SchemaError < CoercionError
70
+ # @return [String, Symbol]
71
+ attr_reader :key
72
+
73
+ # @return [Object]
74
+ attr_reader :value
75
+
69
76
  # @param [String,Symbol] key
70
77
  # @param [Object] value
71
78
  # @param [String, #to_s] result
72
79
  def initialize(key, value, result)
73
- super("#{value.inspect} (#{value.class}) has invalid type for :#{key} violates constraints (#{result} failed)")
80
+ @key = key
81
+ @value = value
82
+ super(
83
+ "#{value.inspect} (#{value.class}) has invalid type "\
84
+ "for :#{key} violates constraints (#{result} failed)"
85
+ )
74
86
  end
75
87
  end
76
88
 
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require "dry/monads/maybe"
5
- require "dry/types/decorator"
3
+ require "dry/monads"
4
+ require "dry/monads/version"
5
+
6
+ if Gem::Version.new(Dry::Monads::VERSION) < Gem::Version.new("1.5.0")
7
+ raise "dry-types requires dry-monads >= 1.5.0"
8
+ end
6
9
 
7
10
  module Dry
8
11
  module Types
@@ -15,7 +18,7 @@ module Dry
15
18
  include Decorator
16
19
  include Builder
17
20
  include Printable
18
- include ::Dry::Monads::Maybe::Mixin
21
+ include ::Dry::Monads[:maybe]
19
22
 
20
23
  # @param [Dry::Monads::Maybe, Object] input
21
24
  #
@@ -99,7 +102,7 @@ module Dry
99
102
  end
100
103
 
101
104
  # @api private
102
- class Schema::Key
105
+ class Schema::Key # rubocop:disable Style/ClassAndModuleChildren
103
106
  # @api private
104
107
  def maybe
105
108
  __new__(type.maybe)
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/monads/result"
3
+ require "dry/monads"
4
+ require "dry/monads/version"
5
+
6
+ if Gem::Version.new(Dry::Monads::VERSION) < Gem::Version.new("1.5.0")
7
+ raise "dry-types requires dry-monads >= 1.5.0"
8
+ end
4
9
 
5
10
  module Dry
6
11
  module Types
@@ -8,7 +13,7 @@ module Dry
8
13
  #
9
14
  # @api public
10
15
  class Result
11
- include Dry::Monads::Result::Mixin
16
+ include ::Dry::Monads[:result]
12
17
 
13
18
  # Turn result into a monad
14
19
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types/container"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Internal container for constructor functions used by the built-in types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types/constructor"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Hash type exposes additional APIs for working with schema hashes
@@ -24,8 +22,8 @@ module Dry
24
22
  # @see Dry::Types::Array#of
25
23
  #
26
24
  # @api public
27
- def schema(*args)
28
- type.schema(*args).constructor(fn, meta: meta)
25
+ def schema(...)
26
+ type.schema(...).constructor(fn, meta: meta)
29
27
  end
30
28
  end
31
29
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types/hash/constructor"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Hash types can be used to define maps and schemas
@@ -51,7 +49,7 @@ module Dry
51
49
  # @api private
52
50
  def weak(*)
53
51
  raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
54
- "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
52
+ "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/main/CHANGELOG.md"
55
53
  end
56
54
  alias_method :permissive, :weak
57
55
  alias_method :strict, :weak
@@ -132,6 +130,3 @@ module Dry
132
130
  end
133
131
  end
134
132
  end
135
-
136
- require "dry/types/schema/key"
137
- require "dry/types/schema"
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Types
5
+ # Implication type
6
+ #
7
+ # @api public
8
+ class Implication
9
+ include Composition
10
+
11
+ def self.operator
12
+ :>
13
+ end
14
+
15
+ # @param [Object] input
16
+ #
17
+ # @return [Object]
18
+ #
19
+ # @api private
20
+ def call_unsafe(input)
21
+ if left.try(input).success?
22
+ right.call_unsafe(input)
23
+ else
24
+ input
25
+ end
26
+ end
27
+
28
+ # @param [Object] input
29
+ #
30
+ # @return [Object]
31
+ #
32
+ # @api private
33
+ def call_safe(input, &block)
34
+ if left.try(input).success?
35
+ right.call_safe(input, &block)
36
+ else
37
+ input
38
+ end
39
+ end
40
+
41
+ # @param [Object] input
42
+ #
43
+ # @api public
44
+ def try(input)
45
+ if left.try(input).success?
46
+ right.try(input)
47
+ else
48
+ Result::Success.new(input)
49
+ end
50
+ end
51
+
52
+ # @param [Object] value
53
+ #
54
+ # @return [Boolean]
55
+ #
56
+ # @api private
57
+ def primitive?(value)
58
+ if left.primitive?(value)
59
+ right.primitive?(value)
60
+ else
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/options"
5
+ require "dry/types/meta"
6
+
7
+ module Dry
8
+ module Types
9
+ # Intersection type
10
+ #
11
+ # @api public
12
+ class Intersection
13
+ include Composition
14
+
15
+ def self.operator
16
+ :&
17
+ end
18
+
19
+ # @param [Object] input
20
+ #
21
+ # @return [Object]
22
+ #
23
+ # @api private
24
+ def call_unsafe(input)
25
+ merge_results(left.call_unsafe(input), right.call_unsafe(input))
26
+ end
27
+
28
+ # @param [Object] input
29
+ #
30
+ # @return [Object]
31
+ #
32
+ # @api private
33
+ def call_safe(input, &block)
34
+ try_sides(input, &block).input
35
+ end
36
+
37
+ # @param [Object] input
38
+ #
39
+ # @api public
40
+ def try(input)
41
+ try_sides(input) do |failure|
42
+ if block_given?
43
+ yield(failure)
44
+ else
45
+ failure
46
+ end
47
+ end
48
+ end
49
+
50
+ # @param [Object] value
51
+ #
52
+ # @return [Boolean]
53
+ #
54
+ # @api private
55
+ def primitive?(value)
56
+ left.primitive?(value) && right.primitive?(value)
57
+ end
58
+
59
+ private
60
+
61
+ # @api private
62
+ def try_sides(input, &block)
63
+ results = []
64
+
65
+ [left, right].each do |side|
66
+ result = try_side(side, input, &block)
67
+ return result if result.failure?
68
+
69
+ results << result
70
+ end
71
+
72
+ Result::Success.new(merge_results(*results.map(&:input)))
73
+ end
74
+
75
+ # @api private
76
+ def try_side(side, input)
77
+ failure = nil
78
+
79
+ result = side.try(input) do |f|
80
+ failure = f
81
+ yield(f)
82
+ end
83
+
84
+ if result.is_a?(Result)
85
+ result
86
+ elsif failure
87
+ Result::Failure.new(result, failure)
88
+ else
89
+ Result::Success.new(result)
90
+ end
91
+ end
92
+
93
+ # @api private
94
+ def merge_results(left_result, right_result)
95
+ case left_result
96
+ when ::Array
97
+ left_result
98
+ .zip(right_result)
99
+ .map { |lhs, rhs| merge_results(lhs, rhs) }
100
+ when ::Hash
101
+ left_result.merge(right_result)
102
+ else
103
+ left_result
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
data/lib/dry/types/lax.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/deprecations"
4
- require "dry/types/decorator"
5
-
6
3
  module Dry
7
4
  module Types
8
5
  # Lax types rescue from type-related errors when constructors fail
@@ -68,7 +65,7 @@ module Dry
68
65
  end
69
66
  end
70
67
 
71
- extend ::Dry::Core::Deprecations[:'dry-types']
68
+ extend ::Dry::Core::Deprecations[:"dry-types"]
72
69
  Safe = Lax
73
70
  deprecate_constant(:Safe)
74
71
  end
data/lib/dry/types/map.rb CHANGED
@@ -14,15 +14,17 @@ module Dry
14
14
  # # => {1 => 'right'}
15
15
  #
16
16
  # type.('1' => 'wrong')
17
- # # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
17
+ # # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1")
18
+ # # AND gteq?(1, "1")
19
+ # # AND lteq?(10, "1") failed)
18
20
  #
19
21
  # type.(11 => 'wrong')
20
22
  # # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
21
23
  #
22
24
  # @api public
23
25
  class Map < Nominal
24
- def initialize(_primitive, key_type: Types["any"], value_type: Types["any"], meta: EMPTY_HASH)
25
- super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
26
+ def initialize(primitive, key_type: Types["any"], value_type: Types["any"], meta: EMPTY_HASH)
27
+ super(primitive, key_type: key_type, value_type: value_type, meta: meta)
26
28
  end
27
29
 
28
30
  # @return [Type]
@@ -100,6 +102,8 @@ module Dry
100
102
  private
101
103
 
102
104
  # @api private
105
+ # rubocop:disable Metrics/PerceivedComplexity
106
+ # rubocop:disable Metrics/AbcSize
103
107
  def coerce(input)
104
108
  unless primitive?(input)
105
109
  return failure(
@@ -131,6 +135,8 @@ module Dry
131
135
  failure(input, MultipleError.new(failures))
132
136
  end
133
137
  end
138
+ # rubocop:enable Metrics/PerceivedComplexity
139
+ # rubocop:enable Metrics/AbcSize
134
140
  end
135
141
  end
136
142
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/deprecations"
4
3
  require "dry/types/builder_methods"
5
4
 
6
5
  module Dry
@@ -8,7 +7,7 @@ module Dry
8
7
  # Export types registered in a container as module constants.
9
8
  # @example
10
9
  # module Types
11
- # include Dry::Types(:strict, :coercible, :nominal, default: :strict)
10
+ # include Dry.Types(:strict, :coercible, :nominal, default: :strict)
12
11
  # end
13
12
  #
14
13
  # Types.constants
@@ -26,10 +25,10 @@ module Dry
26
25
  extend(BuilderMethods)
27
26
 
28
27
  if constants.key?(:Nominal)
29
- singleton_class.send(:define_method, :included) do |base|
28
+ singleton_class.define_method(:included) do |base|
30
29
  super(base)
31
30
  base.instance_exec(const_get(:Nominal, false)) do |nominal|
32
- extend Dry::Core::Deprecations[:'dry-types']
31
+ extend Dry::Core::Deprecations[:"dry-types"]
33
32
  const_set(:Definition, nominal)
34
33
  deprecate_constant(:Definition, message: "Nominal")
35
34
  end
@@ -38,13 +37,16 @@ module Dry
38
37
  end
39
38
 
40
39
  # @api private
40
+ # rubocop:disable Metrics/AbcSize
41
+ # rubocop:disable Metrics/CyclomaticComplexity
42
+ # rubocop:disable Metrics/PerceivedComplexity
41
43
  def type_constants(*namespaces, default: Undefined, **aliases)
42
44
  if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
43
45
  default_ns = :Strict
44
46
  elsif Undefined.equal?(default)
45
47
  default_ns = Undefined
46
48
  else
47
- default_ns = Inflector.camelize(default).to_sym
49
+ default_ns = Types::Inflector.camelize(default).to_sym
48
50
  end
49
51
 
50
52
  tree = registry_tree
@@ -52,7 +54,9 @@ module Dry
52
54
  if namespaces.empty? && aliases.empty?
53
55
  modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
54
56
  else
55
- modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
57
+ modules = (namespaces + aliases.keys).map { |n|
58
+ Types::Inflector.camelize(n).to_sym
59
+ }
56
60
  end
57
61
 
58
62
  tree.each_with_object({}) do |(key, value), constants|
@@ -64,13 +68,16 @@ module Dry
64
68
  constants.update(value) if key == default_ns
65
69
  end
66
70
  end
71
+ # rubocop:enable Metrics/AbcSize
72
+ # rubocop:enable Metrics/CyclomaticComplexity
73
+ # rubocop:enable Metrics/PerceivedComplexity
67
74
 
68
75
  # @api private
69
76
  def registry_tree
70
77
  @registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
71
78
  type = @registry[key]
72
79
  *modules, const_name = key.split(".").map { |part|
73
- Inflector.camelize(part).to_sym
80
+ Types::Inflector.camelize(part).to_sym
74
81
  }
75
82
  next if modules.empty?
76
83
 
@@ -91,9 +98,11 @@ module Dry
91
98
  ns.to_sym unless path.empty?
92
99
  }.compact.uniq
93
100
 
94
- (referenced.uniq - known).each do |name|
101
+ unknown = (referenced.uniq - known).first
102
+
103
+ if unknown
95
104
  raise ArgumentError,
96
- "#{name.inspect} is not a known type namespace. "\
105
+ "#{unknown.inspect} is not a known type namespace. "\
97
106
  "Supported options are #{known.map(&:inspect).join(", ")}"
98
107
  end
99
108
  end
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/deprecations"
4
- require "dry/core/equalizer"
5
- require "dry/types/builder"
6
- require "dry/types/result"
7
- require "dry/types/options"
8
- require "dry/types/meta"
9
-
10
3
  module Dry
11
4
  module Types
12
5
  # Nominal types define a primitive class and do not apply any constructors or constraints
@@ -20,7 +13,7 @@ module Dry
20
13
  include Meta
21
14
  include Builder
22
15
  include Printable
23
- include Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
16
+ include ::Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
24
17
 
25
18
  # @return [Class]
26
19
  attr_reader :primitive
@@ -199,12 +192,8 @@ module Dry
199
192
  end
200
193
  end
201
194
 
202
- extend Dry::Core::Deprecations[:'dry-types']
195
+ extend ::Dry::Core::Deprecations[:"dry-types"]
203
196
  Definition = Nominal
204
197
  deprecate_constant(:Definition, message: "Nominal")
205
198
  end
206
199
  end
207
-
208
- require "dry/types/array"
209
- require "dry/types/hash"
210
- require "dry/types/map"
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
- require "dry/core/class_attributes"
5
- require "dry/types/predicate_registry"
6
-
7
3
  module Dry
8
4
  module Types
9
5
  # PredicateInferrer returns the list of predicates used by a type.
@@ -55,7 +51,7 @@ module Dry
55
51
  end
56
52
 
57
53
  # @api private
58
- def infer_predicate(type)
54
+ def infer_predicate(type) # rubocop:disable Metrics/PerceivedComplexity
59
55
  pred = TYPE_TO_PREDICATE.fetch(type) do
60
56
  if type.name.nil? || self.class.infer_predicate_by_class_name.equal?(false)
61
57
  nil
@@ -175,9 +171,9 @@ module Dry
175
171
  def visit_predicate(node)
176
172
  pred, args = node
177
173
 
178
- if pred.equal?(:type?)
174
+ if pred.equal?(:type?) || !registry.key?(pred)
179
175
  EMPTY_ARRAY
180
- elsif registry.key?(pred)
176
+ else
181
177
  *curried, _ = args
182
178
  values = curried.map { |_, v| v }
183
179
 
@@ -186,11 +182,14 @@ module Dry
186
182
  else
187
183
  [pred => values[0]]
188
184
  end
189
- else
190
- EMPTY_ARRAY
191
185
  end
192
186
  end
193
187
 
188
+ # @api private
189
+ def visit_map(_node)
190
+ raise NotImplementedError, "map types are not supported yet"
191
+ end
192
+
194
193
  private
195
194
 
196
195
  # @api private
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/logic/predicates"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # A registry with predicate objects from `Dry::Logic::Predicates`
@@ -14,20 +12,22 @@ module Dry
14
12
  # @api private
15
13
  attr_reader :has_predicate
16
14
 
15
+ KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
16
+ private_constant(:KERNEL_RESPOND_TO)
17
+
17
18
  # @api private
18
19
  def initialize(predicates = Logic::Predicates)
19
20
  @predicates = predicates
20
- @has_predicate = ::Kernel.instance_method(:respond_to?).bind(@predicates)
21
21
  end
22
22
 
23
23
  # @api private
24
- def [](name)
25
- predicates[name]
24
+ def key?(name)
25
+ KERNEL_RESPOND_TO.bind_call(@predicates, name)
26
26
  end
27
27
 
28
28
  # @api private
29
- def key?(name)
30
- has_predicate.(name)
29
+ def [](name)
30
+ predicates[name]
31
31
  end
32
32
  end
33
33
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # PrimitiveInferrer returns the list of classes matching a type.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Types
5
+ # @api private
6
+ class Printer
7
+ # @api private
8
+ class Composition
9
+ def initialize(printer, composition_class)
10
+ @printer = printer
11
+ @composition_class = composition_class
12
+ freeze
13
+ end
14
+
15
+ def visit(composition)
16
+ visit_constructors(composition) do |constructors|
17
+ @printer.visit_options(composition.options, composition.meta) do |opts|
18
+ yield "#{@composition_class.composition_name}<#{constructors}#{opts}>"
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def visit_constructors(composition)
26
+ visit_constructor(composition.left) do |left|
27
+ visit_constructor(composition.right) do |right|
28
+ yield "#{left} #{@composition_class.operator} #{right}"
29
+ end
30
+ end
31
+ end
32
+
33
+ def visit_constructor(type, &block)
34
+ case type
35
+ when @composition_class
36
+ visit_constructors(type, &block)
37
+ else
38
+ @printer.visit(type, &block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end