Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I force the TypeScript compiler to use nominal typing?

TypeScript uses structural subtyping, so this is actually possible:

// there is a class
class MyClassTest {
    foo():void{}
    bar():void{}
}

// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
    if (f){
        if (!(f instanceof MyClassTest)) {
            console.log("why")
        } 
    }
}

// but it's valid to pass objects, which aren't in fact instances
// they just need to "look" the same, be "structural subtypes"
someFunction({foo(){}, bar(){}})  //valid!

However as the implementation provider of someFunction I would like to really forbid the passing of structurally similar objects, but I really only want to allow real instances of MyClassTest or its subtypes. I want to enforce "nominal typing" at least for some of my own function declarations.

Is this possible?

Background: Consider a case where the objects passed to the API need to be of that type, e.g. because they were produced by a factory which sets some internal state on this object and the object actually has a private interface that someFunction expects to be there in order to work properly. However I don't want to reveal that private interface (e.g. in a typescript definition file), but I want it to be a compiler error if someone passes in a fake implementation. Concrete example: I would like the typescript compiler to complain in this case, even if I provide all the members like so:

//OK for typescript, breaks at runtime
window.document.body.appendChild({nodeName:"div", namespaceURI:null, ...document.body}) 
like image 897
Sebastian Avatar asked Sep 17 '25 08:09

Sebastian


1 Answers

There is no compiler flag to make the compiler behave in a nominal way, indeed there in principle can't be such a flag as that would break a lot of javascript scenarios.

There are several techniques generally used to emulate some kind of nominal typing. Branded types are usually used for primitives (pick one from here as a sample), for classes a common way is to add a private field (a private field will not structurally be matched by anything except that exact private field definition).

Using the last approach your initial sample could be written as:

// there is a class
class MyClassTest {
    private _useNominal: undefined; // private field to ensure nothing else is structually compatible.
    foo():void{}
    bar():void{}
}

// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
    if (f){
        if (!(f instanceof MyClassTest)) {
            console.log("why")
        } 
    }
}


someFunction({foo(){}, bar(){}, _useNominal: undefined})  //err

For your specific scenario, of using append I don't think we can do anything we can do since Node is defined in lib.d.ts as an interface and a const, so augmenting it with a private field is next to impossible (without changes to lib.d.ts), and even if we could it can potentially break a lot of existing code and definitions.

like image 117
Titian Cernicova-Dragomir Avatar answered Sep 21 '25 20:09

Titian Cernicova-Dragomir