简体   繁体   中英

Typescript - type assertion with invalid value is not throwing error

I have a type assertion for the variable 'day', because of this when I assign boolean for the key, it doesn't throw any error.

I am not doing like this const day: Day = 2 because the value I get is a string (which is a dynamic value) and I need to do a type assertion .

Could you please check this below example and suggest what needs to be changed to make it throw an error?

    type Day = 0 | 1 | 2;

type TTime = {
    [K in Day]?: string
}

const day = 2 as Day;

const obj: TTime = {
    [day]: true // Expect this to throw error as 'boolean is not assignable to key
}

Another detailed example of what I am trying to resolve. I have to get the keys of another object(which is by default string[]) and do type assertion .

type Day = 0 | 1 | 2;

type TTime = {
    [K in Day]?: string
}

const result: TTime = {
    2: 'I am result'
}

const keys = Object.keys(result);

let obj: TTime;
keys.forEach((key) => {
    const k = key as unknown as Day;
    const value = result[k]; //Key is string, so needs to be type asserted
    obj = {
        [k]: true //k is 2 in this case, but the value is boolean type and incorrect, this error
                  // is thrown when I remove the type assertion added and hardcode k as 2.
    }
})

You're unfortunately running into two TypeScript limitations or bugs... the first one is minor, but it leads to the major one.


The minor issue is microsoft/TypeScript#13948 , in which an object with a computed property key of a union type is given an index signature which unnecessarily widens the object type to something less useful than you want. For example:

const v = { [Math.random() < 0.5 ? "a" : "b"]: 123 };
// const v: { [x: string]: number;}

The value v will either be {a: 123} or {b: 123} at runtime. It would therefore be nice if TypeScript inferred the type of v to be something like {a: number} | {b: number} {a: number} | {b: number} . Instead, it infers { [x: string]: number } ... so as far as the compiler is concerned, it has absolutely no idea what key names v has; just that any properties it does have are of type number .

In your case, you have

const obj = { [day]: true };
// const obj: { [x: number]: boolean; }

where obj has been widened from something like {0: boolean} | {1: boolean} | {2: boolean} {0: boolean} | {1: boolean} | {2: boolean} {0: boolean} | {1: boolean} | {2: boolean} all the way to {[x: number]: boolean} , which has completely forgotten about Day .

This issue isn't catastrophic, since the actual type is at least compatible with the desired type, but it's suboptimal.


The second bug is microsoft/TypeScript#27144 , in which an object type with an index signature is assignable to a type with all optional properties , even if the property value types are incompatible . This is pretty bad:

const v: { [k: string]: number; } = {a: 1};
const x: { a?: string } = v; // no error!

The type of v has an index signature in which all properties must have a number -type value. Meanwhile, the type of x has a single optional property of a string -type value if it is present. These should not be considered compatible, but they are. And so you can erroneously assign v to x without a compiler warning.

In your case, you have:

const p: TTime = o; // no error

where the TTime type has all optional properties, and is considered compatible with the index-signature type of o , even though boolean and string are not compatible.

Yuck.


So the interaction of those two issues is causing your problem. The "right" thing to do here is probably to wait/lobby for microsoft/TypeScript#27144 to be fixed. If you want to go to that issue and give it a 👍, it wouldn't hurt. It probably wouldn't help much, either. Who knows when or even if it will be addressed.

In the meantime, you have to deal with it.

One way to proceed is to make a workaround for microsoft/TypeScript#13948. Instead of being satisfied with the index signature type, we can write a helper function which directly asserts that the value is of a more useful type. Like this:

const kv = <K extends PropertyKey, V>(
  k: K, v: V
) => ({ [k]: v }) as { [P in K]: { [Q in P]: V } }[K];

The kv function takes a key and a value and produces an object with one property corresponding to that key and value:

const y = kv("a", 123);
// const y: { a: number; }

If we do this when the key is of a union type, we get a union-typed output:

const w = kv(Math.random() < 0.5 ? "a" : "b", 123);
// const w: {a: number} | {b: number}

And so you can use it to make obj :

const obj = kv(day, true);
// const obj: { 0: boolean; } | { 1: boolean; } | { 2: boolean; }

Now that obj does not have an index signature, we don't run into microsoft/TypeScript#27144:

const obj: TTime = kv(day, true); // error,
// boolean is not assignable to string

The compiler is able to see that {0: boolean} | {1: boolean} | {2: boolean} {0: boolean} | {1: boolean} | {2: boolean} {0: boolean} | {1: boolean} | {2: boolean} is incompatible with TTime because boolean is incompatible with string , as desired.

Playground link to code

this way the error will show up;


type Day = 0 | 1 | 2;

type TTime = {
    [K in Day]?: string
}

const day: Day = 2;

const obj: TTime = {
    [day]: true // Expect this to throw error as 'boolean is not assignable to key
}

The type assertion on day is causing the issue. You don't need an assertion (or even an annotation): primitives are immutable in JavaScript and TypeScript, so by declaring the variable using the const keyword, the number will always be the literal type 2 , and TypeScript knows this without additional syntax.

TS Playground

type Day = 0 | 1 | 2;

type TTime = { [K in Day]?: string };

// This is the same type as above, using some type utilities:
// https://www.typescriptlang.org/docs/handbook/utility-types.html
// type TTime = Partial<Record<Day, string>>;

// Don't use a type assertion here. (https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions)
// If you prefer an annotation, you could write it this way:
// const day: Day = 2;
const day = 2;

const obj: TTime = {
  [day]: true, /*
  ~~~~~
  Type of computed property's value is 'boolean', which is not assignable to type 'string'.(2418) */
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM