Search code examples
typescript

TypeScript allows to pass instance of a class but different than the one mentioned in the method signature if it is similar


When two classes have same kind of properties and methods, TypeScript allows to pass instance of a class different than the one mentioned in the method signature. Is there any way to prevent while building (not by checking is instance of while running) ?

abstract class Base {
  abstract display (): void
}

class Person extends Base {
  override display (): void {
    console.log('I am a person')
  }
}

class Animal extends Base {
  override display (): void {
    console.log('I am an animal')
  }
}

const display = function (value: Person) {
  value.display()
}

display(new Person())
display(new Animal()) // THIS SHOULD HAVE FAILED DURING THE BUILD ITSELF, BUT IT DOESN'T

I read about this many places, it seems to me it checks based on the class methods and properties than the class name/reference. Is this how it works and or there is way to prevent it ?


Solution

  • TypeScript uses structural typing: as long as object matches required shape, TS doesn't care if it's same type as in function definition or another very similar type.

    What you want is nominal typing. There are different ways to implement it, depending on your usecase. Simplest one is to add new field to Person class. It can be private to not pollute public API.

    Playground

    abstract class Base {
      abstract display (): void
    }
    
    class Person extends Base {
      private __type = 'person' as const;
    
      override display (): void {
        console.log('I am a person')
      }
    }
    
    class Animal extends Base {
      override display (): void {
        console.log('I am an animal')
      }
    }
    
    const display = function (value: Person) {
      value.display()
    }
    
    display(new Person())
    display(new Animal()) // This now fails
    

    Here is good article about nominal types in TS if you want to dive deeper.