Skip to content

Importing same class that is in 2 different files does not identify it as same class #8052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
adikari opened this issue Apr 13, 2016 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@adikari
Copy link

adikari commented Apr 13, 2016

Lets say I have a class called AbstractClass:

// animal.ts
export abstract class Animal {
   public abstract talk(): String;
}

// cat.ts
import {Animal} from "./animal";

export class Cat extends Animal {
    public talk(): String {
        return "meow";
   }
}

// dog.ts
import {Animal} from "./animal";

export class Dog extends Animal {
   public talk(): String {
       return "whoof whoof";
   }
}

Now I have another module that has dependency on that class.

import {Animal} from "animal-module/animal";

// play-with-animal.ts
export class PlayWithAnimal {
    private animal: Animal;

    constructor(animal: Animal) {
        this.animal = animal;
    }

   public makeAnimalTalk(): void {
        console.log(this.animal.talk());
   }
}
// animal.component.ts
import {PlayWithAnimal} from "play-with-animal-module/play-with-animal.ts"
import {Cat} from "animal-module/cat";

someFunction() {
    let cat: Cat = new Cat();
    let playWithAnimal: PlayWithAnimal = new PlayWithAnimal(cat);

   // here I get error saying:
  // TS2345: Argument of type 'Cat' is not assignable to parameter of type 'Animal'. 'Animal' is not a class derived from 'Animal'. 
}

Here is how the folder structure is. I am using node to resolve the modules.

- app
   animal.component.ts
   // app has dependency of animal-module
- node_modules
   - animal-module
        animal.ts
        cat.ts
        dog.ts
   - play-with-animal-module
      play-with-animal.ts
      // talking-animal-module has dependency of animal-module
      - node_modules
         - animal-module
            animal.ts
            cat.ts
            dog.ts

My finding so far is that, as in the dog.ts or cat.ts, I have imported the Animal as import {Animal} from "./animal";. Notice the relative path.

However in PlayWithAnimal or AppComponent the module is resolved using the node resolution. Somehow typescript thinks that they are separate classes (eventhough) they are same as they are being loaded from different file paths.

If I wrap Animal in a exports module 'SomeModule' and update the code to use SomeModule.Animal then it works fine even if I load the class from 2 different files. However if I do so then I lose ability to import multiple classes like import {Animal, SomeotherClass}.

So my question is:

  1. Is this by design or its an issue?
  2. If this is by design then how are we meant to resolve the modular dependencies?

Thanks.

@aluanhaddad
Copy link
Contributor

@adikari there are several errors in the code you posted.

import "PlayWithAnimal" from "play-with-animal-module/play-with-animal.ts"
import "Cat" from "animal-module/cat";

should be

import { PlayWithAnimal } from "play-with-animal-module/play-with-animal";
import { Cat } from "animal-module/cat";

Only a default export can be imported without destructing or spread syntax, and even then, the quotes around the import names are invalid syntax.

The error message

// TS2345: Argument of type 'Cat' is not assignable to parameter of type 'Animal'. 'Animal' is not a class derived from 'Animal'.

is a bit strange though.

There might be a deeper problem here, but the import syntax is definitely wrong.

PS: What are you compiler settings?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 13, 2016

Classes with private or protected members are compared nominally. i.e. they have to be the exact same class. The idea is that these classes have declared a dependnecy on privatly held fileds, and you can not just substitute something that "looks" like them in place.

If you have two modules that look like one-another, they are not the same module. consider the case where package.json in play-with-animal-module specifies a different version for animal-module than that of your app; at runtime, you really have two modules. and as per your class definition (having a private member) that is not safe to pass one to the other.

so the solution, either do not use privates, or make your public API surface only interfaces, the other alternative, make sure that there is only one play-with-animal-module. this should be the default with npm v3 and later, as all dependencies are flattened.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Apr 13, 2016
@adikari
Copy link
Author

adikari commented Apr 13, 2016

@aluanhaddad My bad. Its just the typo while i was typing in the markdown editor.

@adikari
Copy link
Author

adikari commented Apr 13, 2016

@mhegazy That does make sense. However I am not very clear about,

Classes with private or protected members are compared nominally

Also using interface is not an options and the parent class must be an abstract class as it has more functionality in it that I have not mentioned in the sample code.

So you reckon npm v3 is a solution to this problem where all dependencies will be flattened based upon the version?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 13, 2016

Also using interface is not an options and the parent class must be an abstract class as it has more functionality in it that I have not mentioned in the sample code.

Well then interfaces would not work for your use-case. In general types in TS are compared structurally, i.e. two types are comparable iff they have the same shape. classes/interfaces with private/protected properties are an exception.

class C {
    a: number;
}

var c: C = { a: 1 };  // OK, {a:number} and C are compared structurally


class D {
   private a: number;
}

var d: D = { a: 1 };  // Error, {a:number} and D can not be compared, as D has a private and only instances of D can be used

So you reckon npm v3 is a solution to this problem where all dependencies will be flattened based upon the version?

yes. that assuming that there are no version conflicts between the two importing packages.

@mhegazy
Copy link
Contributor

mhegazy commented May 20, 2016

On a related note, #8486 should handle the npm link scenarios where is really one file on disk but linked from multiple sources.

@mhegazy mhegazy closed this as completed May 20, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants