Encountering two limitations or issues in TypeScript is what you're currently facing... the first one isn't too severe, but it paves the way for the second major issue.
The initial problem can be found with microsoft/TypeScript#13948, where an object featuring a computed property key within a union type receives an unnecessary widening of its object type due to an index signature. Here's an example:
const v = { [Math.random() < 0.5 ? "a" : "b"]: 123 };
// const v: { [x: string]: number;}
The variable v
will be either {a: 123}
or {b: 123}
at runtime. Ideally, TypeScript should infer the type of v
as something like {a: number} | {b: number}
, but instead, it infers { [x: string]: number }
, losing track of the specific key names that v
holds.
In your situation, you have
const obj = { [day]: true };
// const obj: { [x: number]: boolean; }
where obj
has been expanded from something like
{0: boolean} | {1: boolean} | {2: boolean}
all the way to
{[x: number]: boolean}
, disregarding the reference to
Day
.
This issue isn't a disaster because the actual type is at least somewhat compatible with the desired type, although it's not the most optimal solution.
The second bug identified is microsoft/TypeScript#27144, which allows an object type with an index signature to be assigned to a type containing all optional properties, even if the values are incompatible. This scenario proves to be quite problematic:
const v: { [k: string]: number; } = {a: 1};
const x: { a?: string } = v; // no error!
The type of v
enforces that all properties under the index signature contain a numeric value. On the other hand, the type of x
specifies a single optional property holding a string value if present. These types shouldn't align, yet they do, allowing an erroneous assignment from v
to x
without any warning from the compiler.
In your case, consider this:
const p: TTime = o; // no error
Here, the TTime
type includes all optional properties and is deemed compatible with the index-signature type of o
, despite the mismatch between boolean
and string
.
These two issues intertwine to create the problem you are encountering. The best course of action would likely involve waiting for microsoft/TypeScript#27144 to be remedied. Visiting the issue and showing support might help, but there's no guarantee on when or if it will be addressed.
Until then, workarounds are necessary.
An approach to address microsoft/TypeScript#13948 could involve creating a helper function to provide more precise type assertions rather than settling for the generic index signature type. An example is shown below:
const kv = <K extends PropertyKey, V>(
k: K, v: V
) => ({ [k]: v }) as { [P in K]: { [Q in P]: V } }[K];
The kv
function generates an object with a key-value pair based on the inputs provided:
const y = kv("a", 123);
// const y: { a: number; }
If applied to a union type for the key, we get an output reflecting the potential variations:
const w = kv(Math.random() < 0.5 ? "a" : "b", 123);
// const w: {a: number} | {b: number}
This strategy can be used to redefine obj
:
const obj = kv(day, true);
// const obj: { 0: boolean; } | { 1: boolean; } | { 2: boolean; }
By removing the index signature from obj
, we avoid running into issues akin to microsoft/TypeScript#27144:
const obj: TTime = kv(day, true); // error,
// boolean is not assignable to string
The compiler recognizes the incompatibility between
{0: boolean} | {1: boolean} | {2: boolean}
and
TTime
since
boolean
doesn't align with
string
, as intended.
Access the Playground link for code samples here