Skip to content

"Two different types with this name exist, but they are unrelated" emitted when same #26627

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
shanehsu opened this issue Aug 23, 2018 · 7 comments · Fixed by #31545
Closed
Assignees
Labels
Bug A bug in TypeScript

Comments

@shanehsu
Copy link

TypeScript Version: Version 3.1.0-dev.20180823

Search Terms: Different types with same name

Code

export type Constructor<T> = new (...args: any[]) => T
export type MappedResult<T> =
    T extends Boolean ? boolean :
    T extends Number ? number :
    T extends String ? string :
    T


interface X {
    decode<C extends Constructor<any>>(ctor: C): MappedResult<C extends Constructor<infer T> ? T : never>
}

class Y implements X {
    decode<C extends Constructor<any>>(ctor: C): MappedResult<C extends Constructor<infer T> ? T : never> {
        throw new Error()
    }
}

Expected behavior: It should compile without any error. Everything is in the same namespace, so it should be the same type.

Actual behavior: The quoted error below is emitted.

$ tsc tsc-test.ts
tsc-test.ts:14:5 - error TS2416: Property 'decode' in type 'Y' is not assignable to the same property in base type 'X'.
  Type '<C extends import("tsc-test").Constructor<any>>(ctor: C) => import("tsc-test").MappedResult<C extends import("tsc-test").Constructor<infer T> ? T : never>' is not assignable to type '<C extends import("tsc-test").Constructor<any>>(ctor: C) => import("tsc-test").MappedResult<C extends import("tsc-test").Constructor<infer T> ? T : never>'. Two different types with this name exist, but they are unrelated.
    Type 'import("tsc-test").MappedResult<C extends import("tsc-test").Constructor<infer T> ? T : never>' is not assignable to type 'import("tsc-test").MappedResult<C extends import("tsc-test").Constructor<infer T> ? T : never>'. Two different types with this name exist, but they are unrelated.

14     decode<C extends Constructor<any>>(ctor: C): MappedResult<C extends Constructor<infer T> ? T : never> {
       ~~~~~~

Playground Link: Here

Related Issues: None I can find

@jcalz
Copy link
Contributor

jcalz commented Aug 23, 2018

related to #25373 ?

@ghost
Copy link

ghost commented Aug 23, 2018

I think this is a different issue having to do with conditoinal types.

interface I {
    m<T>(): T extends Array<infer U> ? U : never
}

class C implements I {
    m<T>(): T extends Array<infer U> ? U : never { // Error?
        throw new Error();
    }
}

Also, the following should be an error but isn't:

interface I {
    m<T>(): T extends Array<infer U> ? U : never
}

class C implements I {
    m<T>(): T {
        throw new Error();
    }
}

@ghost ghost added the Bug A bug in TypeScript label Aug 23, 2018
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.2 milestone Aug 24, 2018
@ahejlsberg
Copy link
Member

The issue here is that conditional types with inference positions (i.e. infer X declarations in their extends types) introduce distinct type identities for the inferred type variables. When relating conditional types we currently aren't unifying inference type variables, so distinct conditional types with inference positions will always appear unrelated.

The simple workaround is to declare a type alias and use this type alias in both places:

type CtorParams<T> = C extends Constructor<infer T> ? T : never;

interface X {
    decode<C extends Constructor<any>>(ctor: C): MappedResult<CtorParams<T>>
}

class Y implements X {
    decode<C extends Constructor<any>>(ctor: C): MappedResult<CtorParams<T>> {
        throw new Error()
    }
}

This causes the checker to realize the types are the same.

@ahejlsberg
Copy link
Member

@Andy-MS The second issue isn't really meaningful because T is never used in a parameter position. It's basically just another way of writing a type assertion.

Consider this instead:

interface A {
    m<T>(x: T[]): T[];
}

interface B extends A {
    m<U>(x: U): U;
}

The above example is fine because when we instantiate B.m in the context of A.m we infer T[] for U. But we report an error on this:

interface A {
    m<T>(x: T): T[];
}

interface B extends A {
    m<U>(x: U): U;
}

This error is caused by inferring both T and T[] for U which isn't consistent.

@legion-gaming
Copy link

The same sort of issue still exists from #16786. Consider the following (this works in C# if you look at the Enumerable.cs class: https://github.com/Microsoft/referencesource/blob/4.6.2/System.Core/System/Linq/Enumerable.cs)

export interface IOrderedEnumerable<T> extends IEnumerable<T> {
	thenBy<TKey>(selector: (in1: T) => TKey, comparer?: IComparer<TKey>): IOrderedEnumerable<T>;
	thenByDescending<TKey>(selector: (in1: T) => TKey, comparer?: IComparer<TKey>): IOrderedEnumerable<T>;
}

export class OrderedEnumerable<T, TKey> extends Enumerable<T> implements IOrderedEnumerable<T> {
        // other code here omitted

	thenBy(selector: (in1: T) => TKey, comparer?: IComparer<TKey>): IOrderedEnumerable<T> {
		throw new Error("Not implemented");
	}

	thenByDescending(selector: (in1: T) => TKey, comparer?: IComparer<TKey>): IOrderedEnumerable<T> {
		throw new Error("Not implemented");
	}
}

As you see in the concrete class above, the TKey generic should be inferred from the class for those thenBy functions, but the compiler fails with the "Two different types with this name exist, but they are unrelated" error.

@shayded-exe
Copy link

@ahejlsberg Your workaround works great, thanks!

This is pretty confusing when you run into it the first time. The documentation on conditional and inferred types should mention something about this.

weswigham added a commit that referenced this issue Aug 19, 2019
…the target conditional (#31545)

* Instantiate generic conditional infer source types in the context of the target conditional

* Add test case from #26627
timsuchanek pushed a commit to timsuchanek/TypeScript that referenced this issue Sep 11, 2019
…the target conditional (microsoft#31545)

* Instantiate generic conditional infer source types in the context of the target conditional

* Add test case from microsoft#26627
@ties-s
Copy link

ties-s commented Sep 12, 2021

@ahejlsberg this work around works! Finally found it after wasting hours searching for the solution.

This is especially annoying when trying to wrap a type as a promise, since that also doesn't work correctly with specifying a separate promise-wrapped type.

Any new on if this is going to be fixed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
8 participants