The semantics of type restictions

Every type in a parameter restriction is effectively a covariant type parameter. One way to move forward is to make the covariance explicit via Upper-bounded free variables in def restrictions · Issue #11908 · crystal-lang/crystal · GitHub

The entire return type restriction is covariant. It must also name an actual valid type, so the following is currently illegal:

def foo(x : Array(Array)) : Array(Array)
  x
end

foo([[1]]) # Error: method ::foo must return Array(Array(T)) but it is returning Array(Array(Int32))

Following the example Doubt about overloading - #4 by HertzDevil, if you know C++, the snippet in the OP would be analogous to:

std::convertible_to<std::variant<int, std::nullptr_t>> auto
foo(std::convertible_to<int> auto x) {
  return
    // x == 0 ? nullptr :
    1 / x;
}

foo(3) + 1;
1 Like