Take the example of a variant type, which is what I'm most interested in.
You have something like type X = int32 | string | boolean | SomeCustomType
When you then write a function that accepts X as a type you need to be able to pass any of these things into that function. To do that you need a auto user defined type that can carry the information, in C that might look like this
struct { union { int a; char* b; boolean c; } data; Tag type, }
then your type assertions can work on this. If you wrote this in my fancy language:
if x != nil { // x cannot be nil here x.foo }
the equivalent C code for this would be something like
void f(X x) { if (x.type != WELL_KNOWN_NIL) { x.data.foo // we know it's safe to access foo (assuming we typed X as Foo | nil) } }
Trying to solve this at compile time without any runtime checks might be possible but I don't know that it would be better, it might be too slow if I have to solve some nasty combinatorial problems. This is at least somewhat easy to explain.
The idea from a programming point of view is really nice and in a dynamic programming language environment everything is an object and you can pass whatever through any function. This doesn't work all that well if you want to translate your program into a statically typed environment, in a cost effective way.
You need some way to reason about the type information that hopefully doesn't create memory allocations all over the place.
This can be done by introducing tagged data types but it can also lead to a combinatorial nightmare where you need to generate a lot of extra code.
In general, I don't think all this complexity is warranted in a statically typed environment and depending what you are doing this doesn't end up being that important. Even if, it's a nice to have, it's mostly that, a nice to have.