Types are compilation time values of type "type". As such, they can be passed around to compilation time functions, manipulated by operators, and so on.
It is possible to attach predicates to almost any type, which are verified during compilation. See verification for details.
A special operator, where, is used to append additional predicates to a type. The predicates themselves are expressions enclosed in brackets, inside of which "@val" represents the value.
sint(32) where [@val>=0 @val<10] // A sint(32) type whose value is restricted to the [0,10[ range.
Those aren't supported yet, but some of the groundwork is there as most of the builtin types are implemented separately from the core of the compiler, so it is mostly a matter of exposing the necessary apis as builtin compile-time functions callable by the language and wrapping them in the std library to offer a simple and familiar way to define custom types.
See internals for more details.
There is a beginning of support in that a compilation time function can be called and return a type. However, this doesn't play along with the template system. It will be later changed into a concept of type constructors (a compilation time function call with brackets instead of parentheses), which will build and return a normalized type representation that can be either a concrete type or a template expression (if the type constructoir was passed template expressions as parameters).
When various operations happen on a value, we invoke an overloadable compilation time function on it. Those are called extension points.
Extension points allow to customize the behavior of types.
For instance, when a value is dropped (ie not used, such as when calling a function that returns a result without using it as part of an expression), a DropValue() extension point is invoked.
If there is no overload for a given type for this extension point, then dropping a value of this type will raise an error, thus reproducing the behavior of the [[nodiscard]] c++ attribute, where a value returned by a function is not allowed to be ignored.
On the other hand, a localvar value (most grammatical constructs are actually compile time values in goose), when dropped, extends its visibility and life duration to the entire block (whereas when not dropped it is visible only within the statement it is in)
A tuple, when dropped, in turn drops all of its members. Those members can in turn be for instance local variables themselves, which will therefore each extend their visibility. (see tuples)
Other extension points currently are the local variable default initializer, allowing types to optionally define a default initialization, the non default local variable initializer, and "DestructValue", called on values going out of scope.
In the future, other ideas of extension points allowing types to customize their behavior could be operations such as "capture a variable of type X in a lambda", "pass X as a parameter to a function", and so on.
Extension points, used in conjonction with contracts, can allow to tailor the behavior of types to implement linear types (types whose values have to be used once but only once), borrow checking, and so on.
Most importantly, it will allow all of those things to be done from the library. Note that extension point overload can be intrinsic functions, which allow for low level manipulation of value and types representations and of the output CFG. See internals.