Your exploration is really good, and there are in fact some things you can reason about. I don't want you to walk away thinking that the compiler is just completely fickle.
Your `alt13` is actually a good illustration of it making sense. Even though I've seen confusion on the issue tracker as to whether `D` contains a type or a value, it is a type variable, and it contains a type. That type is `123`, which is a type that only has one possible value, a bit like `void` or `nil`, or `range[123,123]` But, unlike `range[123,123]`, `D` and the value of a variable of type D can be treated as the same thing, which is confusing, but convenient. On the same lines, I find it easier to reason about `ix.D` as being sugar for `ix.typeof.D`, because in my mental model, type parameters belong to the type, not the instance. Going back to your first idea macro foo(ix:static int) = newLit(X) func idea1(ix:Ix):int = ix.D var z:Ix[123] echo foo(idea1(z)) Run why doesn't this work? well, because you can't return a static int from a proc. An instance of `static int` is a type, and procs can't return a type. (This is if we think of static T as a higher order type, yes it sometimes makes more sense to think of it just as a T whose value is known at compile time, like `proc regex(s:static string):Regex` or something. But I would argue, if you can implicitly convert a static T to a value of type T, you can do the reverse **if you 're in a static context**) Aha, so how to get our int into a static context? You could assign it to a const, maybe? const tmp = idea1(z) # 😞 Run Oh no this is never going to compile, because z doesn't exist at compile time. The compiler isn't smart enough to know that `idea1` is only using statically-known properties of z. One way to express that we don't need any run-time information about z is: const tmp = idea1(z.typeof.default) #😊 Run And indeed this is enough. echo foo(idea1(z.typeof.default)) Run here I'm explicitly converting from the value, to type, and back to value, which I'm arguing is what's happening implicitly in `alt13` To reiterate, having that mental model of static type parameters being _types_ and not values is crucial. * * * Bearing that in mind, what's going on here: Test2[X] = object when X.typ == ftA: prefix: string other: int view: array[X.N, int] Run This shouldn't instantiate, because 0.typ doesn't exist, so please file a bug, and maybe your commented out version should? Maybe? or maybe it shouldn't compile, because in `type Test3[X: static Bar]` X is a variable of the _typeclass_ Bar, not a specific Bar[N], so it doesn't have access to N, because you didn't say `type Test3[N: static int; X: static Bar[N]]` Here are two ways to make it work: type Foo = object typ: FooType N: int Bar[N:static int] = object typ: FooType type Test2[X: static Foo] = object when X.typ == ftA: prefix: string other: int view: array[X.typeof.default.N, int] ## yup, got that old 'couldn't instantiate X' message, template Test3Impl[N:static int](b:Bar[N]):type = type Impl = object when b.typ == ftA: prefex: string other: int virw: array[N,int] Impl type Test3[X:static Bar] = Test3Impl(X) Run here's that template pattern again it let's us reify Bar[N] along with it's typ field so we can actually use them