Skip to content
New issue

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

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

Already on GitHub? # to your account

Generics accept non-type as a type variable #27966

Closed
mathiajusth opened this issue Oct 18, 2018 · 4 comments
Closed

Generics accept non-type as a type variable #27966

mathiajusth opened this issue Oct 18, 2018 · 4 comments
Labels
Duplicate An existing issue was already created

Comments

@mathiajusth
Copy link

mathiajusth commented Oct 18, 2018

TypeScript Version: 3.1.2

Expected behavior:
TS should not allow us to use a value instead of a type as argument to Generic (it is a type error)
(e.g - see code below - IType<E.a> should error. Only IType<E> is correct for E.a is not a type but a value of type E)

Actual behavior:
TS treats value of an enum like a type when fed to Generic. It results in type inference problems (see the example below)

Code

enum E {
	a = 'a',
	b = 'b',
}

interface IType<T> {
	type: T
}
 
interface IArrayA {
	listOfTypeA: Array<IType<E.a>>
}

interface IArrayB {
	listOfTypeB: Array<IType<E.b>>
}

const a: IArrayA = {
	listOfTypeA: [{type: E.a}],
}

const b: IArrayB = {
	listOfTypeB: a.listOfTypeA.map(() => ({type: E.b})), // ERROR: Type 'E' is not assignable to type 'E.b'.
}

Error explanation:
Ofc TS's type inference infers that {type: E.b} is of type {type: E} as it should. From that it infers b: {listOfTypeB: Array<{type: E}>}. But we wanted b: {listOfTypeB: Array<{type: E.b}>}.

Workaround: You can override TS's type system and tell it that E.b is of type E.b (although E.b is not a type...)
listOfTypeB: a.listOfTypeA.map(() => ({type: E.b as E.b})),

@ahejlsberg
Copy link
Member

First, E.a and E.b are types. They represent the two possible singleton values of the enum, and type E is exactly equivalent to E.a | E.b.

The real issue here is that the object literal { type: E.b } in the arrow function passed to map has its type inferred as { type: E } because it isn't contextually typed. We're tracking this exact issue in #11152, so I'm marking this as a duplicate.

@ahejlsberg ahejlsberg added the Duplicate An existing issue was already created label Oct 18, 2018
@mathiajusth
Copy link
Author

Thank you for a fast response.
Having every value of a sum type be a type inhabited by a single value that is itself is highly disturbing, though. 😞

@fatcerberus
Copy link

@mathiajusth I don’t know if you know this but TypeScript in general has a concept of “literal types”, e.g. ”foo” | “bar” is a type that can only be the string “foo” or the string “bar”—and nothing else. It’s a bit jarring at first but quite useful once you get the hang of utilizing it.

@typescript-bot
Copy link
Collaborator

This issue has been marked as a duplicate and has seen no activity in the last day. It has been closed for automatic house-keeping purposes.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants