Skip to content
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

Feature Request: Allow typeof with index types #22194

Closed
SLaks opened this issue Feb 26, 2018 · 12 comments
Closed

Feature Request: Allow typeof with index types #22194

SLaks opened this issue Feb 26, 2018 · 12 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@SLaks
Copy link

SLaks commented Feb 26, 2018

Revised feature request: Please allow typeof in mapped types:

Given a type like this:

type Enum<TEnum> = {
    [TName in keyof typeof TEnum]: TEnum[TName]
}

I want to write

type Schema<TObject> = {
    [TName in keyof TObject]: Enum<typeof (TObject[TName])>
}

This way, given the property declared as foo: SomeEnum, the mapped type would produce foo: Enum<typeof SomeEnum>, which would then match actual property foo: SomeEnum.

TObject[TName] is a value, so typeof should apply. However, this currently gives a syntax error.
Removing the parentheses complains (correctly) that TObject itself is a type, not a value.


TypeScript Version: 2.7.0

Search Terms: typeof keyof generic

Playground Link:
Minimal: http://www.typescriptlang.org/play/index.html#src=enum%20Color%20%7B%0D%0A%20%20%20%20Red%2C%20Green%2C%20Blue%0D%0A%7D%0D%0A%0D%0Atype%20Enum%3CTEnum%3E%20%3D%20%7B%0D%0A%20%20%20%20%5BTName%20in%20keyof%20TEnum%5D%3A%20TEnum%5BTName%5D%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20MyClass%20%7B%0D%0A%20%20%20%20color%3A%20Color%3B%0D%0A%7D%0D%0A%0D%0Aconst%20test%3A%20Enum%3Ctypeof%20(MyClass%5B'name'%5D)%20%3E%20%3D%20Color%3B%0D%0A

With motivating real-world example:
http://www.typescriptlang.org/play/index.html#src=enum%20Color%20%7B%0D%0A%20%20%20%20Red%2C%20Green%2C%20Blue%0D%0A%7D%0D%0A%0D%0Aenum%20Shape%20%7B%0D%0A%20%20%20%20Square%2C%20Circle%2C%20Triangle%0D%0A%7D%0D%0A%0D%0Atype%20Enum%3CTEnum%3E%20%3D%20%7B%0D%0A%20%20%20%20%5BTName%20in%20keyof%20TEnum%5D%3A%20TEnum%5BTName%5D%3B%0D%0A%7D%3B%0D%0A%0D%0A%2F%2F%20Simple%20sample%3A%0D%0Aconst%20test%3A%20Enum%3Ctypeof%20Shape%3E%20%3D%20Shape%3B%0D%0A%0D%0A%2F%2F%20Real-world%20sample%20usage%3A%0D%0Ainterface%20MyClass%20%7B%0D%0A%20%20%20%20name%3A%20Date%3B%0D%0A%20%20%20%20color%3A%20Color%3B%0D%0A%20%20%20%20shape%3A%20Shape%3B%0D%0A%7D%0D%0Atype%20Schema%3CT%3E%20%3D%20%7B%0D%0A%20%20%20%20%5BTName%20in%20keyof%20T%5D%3A%20(%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20For%20classes%3A%0D%0A%20%20%20%20%20%20%20%20%7B%20new()%3A%20T%5BTName%5D%20%7D%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20For%20enums%3A%0D%0A%20%20%20%20%20%20%20%20%7CEnum%3CT%5BTName%5D%3E%0D%0A%20%20%20%20)%0D%0A%7D%0D%0A%0D%0Aconst%20myClassSchema%3A%20Schema%3CMyClass%3E%20%3D%20%7B%0D%0A%20%20%20%20name%3A%20Date%2C%0D%0A%20%20%20%20color%3A%20Color%2C%0D%0A%20%20%20%20shape%3A%20Shape%2C%0D%0A%7D%3B

@s-ve
Copy link

s-ve commented Feb 27, 2018

I don't think that supporting typeof TEnum here would make much sense since TEnum can only refer to something that is already a type. typeof always expects a value as operand.

You can refactor your code like this :

type Enum<TEnum> = {
    [TName in keyof TEnum]: TEnum[TName]
};

const test: Enum<typeof Color> = Color;

But then it would be equivalent to simply writing :

const test = Color;

Could you maybe clarify the purpose of your Enum<TEnum> type ? 🙂

@ghost
Copy link

ghost commented Feb 27, 2018

Got this working for string enums:

enum Color {
    Red = "Red", Green = "Green", Blue = "Blue",
}

enum Shape {
    Square = "Square", Circle = "Circle", Triangle = "Triangle",
}
type Enum<TEnum extends string> = {
    [TName in TEnum]: string;
};
const test: Enum<Shape> = Shape;

// Real-world sample usage:
interface MyClass {
    name: Date;
    color: Color;
    shape: Shape;
}
type SchemaForType<T> = T extends string ? Enum<T> : { new(): T }
type Schema<T> = {
    [TName in keyof T]: SchemaForType<T[TName]>
}
const myClassSchema: Schema<MyClass> = {
    name: Date,
    color: Color, // There is correctly a compile error if you write `color: Shape`!
    shape: Shape,
};

Unfortunately this does not work for normal number enums right now, because we can't put those in mapped types:

type Enum<TEnum extends number> = {
    [TName in TEnum]: string; // Error: TEnum is not a string type...
};
const test: Enum<Shape> = Shape;

Closest issue I can find for that is #22105

@SLaks
Copy link
Author

SLaks commented Feb 27, 2018

@s-ve See the end of my playground link.

Basically, I want to use Enum<T> together with mapped types, to create a type that maps an object to its property types.
Something like

type Schema<TObject> = {
    [TName in keyof TObject]: Enum<TObject[TName]>
}

interface MyClass {
    color: Color;
}

const schema: Schema<MyClass> = {
    color: Color,
};

My feature request should actually be to allow [TName in keyof TObject]: Enum<typeof (TObject[TName])>, which is currently a syntax error.

@ghost
Copy link

ghost commented Feb 27, 2018

@s-ve is correct that you can't get the typeof something that's already a type. This might be confusing since there is a type Color and a value Color that share the same name, but they're two different things. If we have x: Color there's no way to get access to the value Color. You can't use typeof typeof x since typeof x is a type, not a value. typeof Color works because we interpret Color as referring to the value and not the type.

@SLaks
Copy link
Author

SLaks commented Feb 27, 2018

But TObject[TName] is a value, so typeof that should work.

@ghost
Copy link

ghost commented Feb 27, 2018

No, TObject[TName] is the type of the property.

@SLaks SLaks changed the title Feature Request: Allow typeof with generic parameters / keyof Feature Request: Allow typeof with mapped types Feb 27, 2018
@SLaks SLaks changed the title Feature Request: Allow typeof with mapped types Feature Request: Allow typeof with index types Feb 27, 2018
@SLaks
Copy link
Author

SLaks commented Feb 27, 2018

@andy-ms Your solution only works when the enum values match their names (since you're using TName in TEnum instead of keyof).

@SLaks
Copy link
Author

SLaks commented Feb 27, 2018

I see; I was misunderstanding how typeof works for enums.

Is there any way to access the static members of a type?

Can you add such a feature?

@ghost
Copy link

ghost commented Feb 27, 2018

Just realized my example wouldn't work because T extends string ? Enum<T> : { new(): T } maps over the union T, so it just has to be Enum<"Red"> | Enum<"Green"> | Enum<"Blue">, while I wanted Enum<"Red" | "Green" | "Blue">. @sandersn Was there a workaround for that?

Is there any way to access the static members of a type?

We don't have the notion of a static member of a type -- a type just describes instances. If you write class C { static a() {} b() {} }, the type C just describes { b(): void } and has no reference to the static side of the class, which is described by a separate type typeof C (where C is the value, not the type).

@mhegazy
Copy link
Contributor

mhegazy commented Mar 1, 2018

[T] extends [string] ? ... will not distribute

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Mar 1, 2018
@s-ve
Copy link

s-ve commented Mar 1, 2018

@mhegazy Unfortunately Typescript will not infer that T extends string in the conditional true branch.

I don't know if this is expected or not.

Related : #21870

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants