- Values dependent on values (algorithms)
- Values dependent on types (parametric polymorphism)
- Types dependent on types (type-level functions)
This is one dimension weaker than Calculus of Constructions (CoC) [1], which permits types dependent on values (so-called "dependent types"), but we don't need them until we want to prove some properties about our code (dependent types correspond to logical predicates). Some tasks handled by metaprogramming however may not be handled even by a type system that permits type-level functions; for this reason, probably we have to include some sort of simple syntax transformations, such as those found in Idris [2].
I expatiated in one of my posts [3] on why static languages duplicate their language concepts. This writing also includes Zig and Idris.
[1] https://gist.github.com/Hirrolot/89c60f821270059a09c14b940b4...
[2] http://docs.idris-lang.org/en/latest/tutorial/syntax.html
[3] https://hirrolot.github.io/posts/why-static-languages-suffer...