Improved modeling of TypeScript generics and their concretizations

Moose
TypeScript
Author

Nour Ayechi

Published

October 22, 2024

The ts2famix npm package provides the ability to analyze TypeScript projects by breaking down their components and generating a model with detailed information. However, both ts2famix and its corresponding metamodel initially had limitations in accurately recognizing parametric elements and concretization relationships. During my Summer 2024 internship, I focused on improving the recognition of these elements and their representation, enhancing their accuracy in both the ts2famix-generated JSON file and our metamodel.

Assumptions

In this post, we assume familiarity with TypeScript’s generic types and their usage. We’ll explore how generics and concretization relationships are represented in both our metamodel and ts2famix.

Understanding Genericity and Concretization

Genericity refers to the ability to define classes, methods, or types that can operate on different data types. In TypeScript, generics allow developers to write reusable code that can handle various data types while maintaining type safety.

// Parametric Class
class ClassA< T > {
    private value: T;
    constructor(value: T) {
        this.value = value;
    }
}

For example, a generic class like ClassA can be defined with a type parameter T, making it flexible to work with different types, but it cannot be used without specifying the type for T.

Concretization occurs when these generic types are assigned specific values or types, turning abstract type parameters into concrete instances.

class ClassB extends ClassA< string > {
  // In Famix, ClassA is modeled as a ParametricClass with type string
  // ClassB and string are modeled as Class and PrimitiveType, respectively
}

In the case of ClassA, concretization would involve setting T to a specific type like string.

ClassA< T > { /* ... */ }

ClassB extends ClassA< string > { /* ... */ }

// Modeling the relations in Famix:
// ClassA< T >  ==Concretization==>  ClassA< string >
// T  ==ParameterConcretization==>  string

In this example, we have two classes: ClassA< T >, a generic class with the parameter T, and ClassA< string >, a concrete class where T is specified as string. This illustrates a concretization relationship between ClassA< T > and ClassA< string >, as well as a parameter concretization relationship between the generic parameter T and the concrete parameter string.

Note

This example is explained in detail (with an example in Java) throughout this blog post.

Implementation in the FamixTypeScript Metamodel

Now that we’ve established a theoretical understanding of generics and concretization, we can see how these concepts were implemented in our metamodel.

TypeScript Metamodel (Famix) with generics and concretizations.

Our metamodel includes 8 new classes:

  • Five representing the core generic structures: ParametricClass, ParametricInterface, ParametricFunction, ParametricMethod, and ParametricArrowFunction.
  • A ParameterType, which refers to the parameters of a parametric element.
  • Two classes, Concretisation and ParameterConcretisation, defining relationships between parametric elements and between parameter types, respectively.

The use of traits has been key in organizing parametric elements and concretizations, allowing us to handle them in a structured, modular way.

We have 5 new traits in our meta-model:

  • TParametricEntity is a general type used by all parametric entities, including ParametricClass, ParametricInterface, ParametricFunction, ParametricMethod, and ParametricArrowFunction.
  • TConcreteParameterType represents concrete parameters.
  • TGenericParameterType represents generic parameters.
  • TConcretisation establishes a link between two TParametricEntity instances. A TParametricEntity can have one or more concretizations with other TParametricEntity objects. Each TParametricEntity that is a concretization of another holds a reference to its generic entity.
  • TParameterConcretisation works similarly to TConcretisation, but instead of linking two TParametricEntity instances, it connects a TConcreteParameterType with a TGenericParameterType. A TGenericParameterType can have multiple concretizations, and a TConcreteParameterType contains references to its generics.
Note

These relations are explained in detail throughout this blog post.

Unified Approach in the Metamodel and ts2famix

Both our metamodel and the ts2famix npm package share a consistent approach to representing generics and concretization relationships. In our metamodel, parametric elements and their concretizations are modeled clearly to reflect the relationships between abstract types and their concrete instances. These representations are aligned with how ts2famix handles generics in TypeScript projects, ensuring that improvements in the recognition of parametric elements are mirrored in both the model and the code analysis process.

TypeScript Metamodel

TypeScript Metamodel

Conclusion

In this post, we’ve explored how generics and concretization relationships are modeled both in our metamodel and the ts2famix npm package. By ensuring a consistent approach across both systems, we’ve enhanced the accuracy and usability of these parametric elements in TypeScript project analysis.