How about an annotation @alwaysExpand for type aliases?

If you (or at least, I) write generic code, I end up using a lot of type aliases as type variables in the same way as value variables, for example

  type TypeParam = ComplexTypeDefinition
  new HigherType[TypeParam, TypeParam, TypeParam].

In my mind, a lot of such type aliases are implementation artifacts, and because they are local/specific to a use case, add another barrier for an unfamiliar reader wanting to understand what they are dealing with. On the other hand, some type aliases are almost opaque types. For example,

  type EmptyTuple = EmptyTuple.type

Here, as a reader, I would like to deal simply with EmptyTuple, rather than EmptyTuple.type. This is a minimalist example, but I have a alot of type aliases which serve as some specific instantiations/subtypes of a generic trait/class. When they are commonly used, I’d rather not see them expanded to the underlying template type, which is much longer and not recognisable at the first glance.

So, to me, type aliases can be roughly divided into:

  1. intermediate/helper type variables/values
  2. a true type alias
  3. a ‘semi-opaque’ type, which is almost used as such, but preserving the complete information for checking conformance and retain the interface of the aliased type.

While they all function the same way, I’d argue that they are different in nature, I would like the compiler (and especially IDE) to present them accordingly: always expand the first, and always present the type in a version where expansion stops at the third case (in addition to the fully expanded type for easier comparison). Especially the latter would improve the readability of errors in my project: some mistakes can be easier to understand when the reported types are more concise, while more subtle ones would require digging deeper and looking into the full expansion.

For example, let us have:

  trait Template {
    type Item
    type Modifier[I <: Item]

    def m(...) :Modifier[Item]
  }
  trait Abstract extends Template {
    type Modifier[I <: Item] = (Item, Item)
  }
  trait Concrete extends Abstract {
     type Item = String
  }

If I call c.m(...) forSome { val c :Concrete }, I’d like to see the result as (String, String), as Modifier and Item are clearly here just a method of composing the return type, rather than entities in themselves. This is perhaps not as clear in case of the compiler, but would be an invaluable hint for IDE, for example when adding an explicit return type to a method from the inferred type of the returned value.

While the above division into three categories may be seen as splitting hairs, I think there is a clear case that there is indeed some duality in how type aliases are used. We have a precedence in a similarily presentation oriented @showAsInfix.

Thoughts? Is it something so easy to implement that would make a good first issue for someone who haven’t seen compiler code before?