# Confusing type error

**URL:** <https://forum.crystal-lang.org/t/confusing-type-error/1602>\
**Category:** Help & Support\
**Created:** [January 10, 2020, 1:50am UTC](https://forum.crystal-lang.org/t/confusing-type-error/1602 "2020-01-10T01:50:05Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![caspiano](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/caspiano/32/527_2.png) [@caspiano](https://forum.crystal-lang.org/u/caspiano)\
**Post date:** [January 10, 2020, 1:50am UTC](https://forum.crystal-lang.org/t/confusing-type-error/1602/1 "2020-01-10T01:50:05Z")

</div>

First time caller, long time listener.  
I have an issue that could use some elucidation.

The intent is to traverse a structure and transform a set of primitive types to string, whilst preserving structure. Given this example code…

```crystal
def to_stringly(value)
  case value
  when Array, Tuple
    value.map { |v| to_stringly(v) }
  when Hash
    value.transform_values { |v| to_stringly(v) }
  when NamedTuple
    to_stringly(value.to_h)
  when String, Bool
    value
  else
    value.to_s
  end
end

body = {
  :compare => [
    {
      :key => "key",
      :value => 0,
      :target => "VERSION",
      :result => "EQUAL",
    },
  ],
  :success => [
    {
      :request_put => {
        :key => "key",
        :value => "value",
      },
    },
  ],
  :failure => [] of Nil,
}

to_stringly(body)

```

Results in the following error…

```auto
error in line 47
Error: instantiating 'to_stringly(Hash(Symbol, Array(Hash(Symbol, Hash(Symbol, String))) | Array(Hash(Symbol, Int32 | String)) | Array(Nil)))'

error in line 14
Error: instantiating 'to_stringly((Array(Hash(Symbol, Hash(Symbol, String))) | Array(Hash(Symbol, Int32 | String)) | Array(Nil)))'

error in line 8
Error: instantiating 'to_stringly((Hash(Symbol, Hash(Symbol, String)) | Hash(Symbol, Int32 | String)))'

error in line 10
Error: type must be Hash(Symbol, String), not (Hash(Symbol, String) | String)

```

Shouldn’t the union be an acceptable argument given the type refinement of the case statement?

Thanks!

---

<div class="post-metadata">

**Author:** ![caspiano](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/caspiano/32/527_2.png) [@caspiano](https://forum.crystal-lang.org/u/caspiano)\
**Post date:** [January 13, 2020, 2:27am UTC](https://forum.crystal-lang.org/t/confusing-type-error/1602/2 "2020-01-13T02:27:45Z")

</div>

This was the solution. Any ideas why the type coercion for the `String` case was necessary?

```crystal
def to_stringly(value)
    case value
    when Array, Tuple
      value.map { |v| to_stringly(v) }
    when Hash
      value.transform_values { |v| to_stringly(v) }
    when NamedTuple
      to_stringly(value.to_h)
    when String
      value.as(String)
    when Bool
      value
    else
      value.to_s
    end
  end

```

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [January 13, 2020, 9:05pm UTC](https://forum.crystal-lang.org/t/confusing-type-error/1602/3 "2020-01-13T21:05:02Z")

</div>

The issue is related to how the method is instantiated for each type.

A workaround is to use split the case in different methods **and use forall**. The ordering is important.

```auto
def to_stringly(value)
  value.to_s
end

def to_stringly(value : Nil)
  value
end

def to_stringly(value : Array(T)) forall T
  value.map { |v| to_stringly(v) }
end

def to_stringly(value : Hash(K, V)) forall K, V
  value.transform_values { |v| to_stringly(v) }
end

```

Note that if `Array` and `Hash` are defined with no `forall` you will end up in the same issue:

```auto
def to_stringly(value : Array)
  value.map { |v| to_stringly(v) }
end

def to_stringly(value : Hash)
  value.transform_values { |v| to_stringly(v) }
end

```

```auto
 38 | value.transform_values { |v| to_stringly(v) }
                             ^
Error: type must be Hash(Symbol, String), not (Hash(Symbol, String) | String)

```
