It's Objects all the way down
Side Note: There are varying opinions on the definition of strong-typed language, and how that may or may not interact with the definition of static-typed language.
Now, I also need the classes to work as first class citizens so that classes themselves could be taken as function parameters or returned.
This is a good thought, and you should run with it. What if you were able to treat all things uniformly as Objects? Let's see where that goes.
Everything is an object. In many systems (Python, Java, etc.), there is a root class called
Object. Any class inherits from this one. What if we made this as normal an object as we can? It would be an instance of some class, let's call it
Type is itself an object as well, and since it counts as a class it must inherit from
Object. This is a circular dependency, which you'll have to resolve.
Type here is a class whose instances are other classes, that makes it a metaclass. It's also just an object, so you can put it into variables and pass it in and out of functions.
You also ask
[I]s it ok to treat classes and metaclasses the same way since they represent different levels of conceptualisation ?
This is fine. In the runtime view that everything is just an object, your "different levels of conceptualisation" is just an illusion. Your type checker may have a different opinion on that, which is perfectly fine.
For that matter, things like a class' attribute or method can easily be objects themselves. An Attribute could just be an object with get and set methods (that explicitly or implicitly take the instance on which to operate), while a method could be a function that (again explicitly or implicitly) takes a class instance as an argument.
In the comments, you add
I don't see how it would be possible with the language to write a generic sort function with a such signature :
sort ( data : Set<MyClass> , attr : Attribute ) with
Attribute being the name of the class attribute to sort by.
You should be able to get away with this by stealing the concept of Generics from other languages, and maybe inventing some syntax to define dependencies in argument lists. For example, you could do something like this
sort ( data : Set<T> , attr : T::(String, Integer) )
which would mean the first argument must be a
Set containing some type
T, and the second argument must be an
Attribute object compatible with (but not necessarily directly on) type
T, which takes a
String and an