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})
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With