#Why this code does not compile?

56 messages · Page 1 of 1 (latest)

silver harbor
#

Hello! I have the following code:

type ToBoolean<T> = T extends boolean ? boolean : T;

function f<T extends boolean>() {
  const x: ToBoolean<T> = false;
  return x;
}

TypeScript shows error over const x:

Type 'false' is not assignable to type 'ToBoolean<T>'.ts(2322)
const x: ToBoolean<T>

Can somebody explain why this does not compile?
Thank you!

finite stone
#

T could be never.

#

What's the use case for the code?

silver harbor
#

I have some with default values. From that object I construct current values types. For strings it works fine, but if some default value is false, in that case type of current value is also strictly false, but I want boolean.

finite stone
#

Can you show the full code?

silver harbor
#

Sorry, it can take time. I know that I can solve the task itself in another way. My question is more about the particular snippet. Could you explain how never influence the result? And how to fix this snippet to make it working?

void starBOT
#
type ToBoolean<T> = T extends boolean ? boolean : T

type Result = ToBoolean<never>
//   ^? - type Result = never
finite stone
#

So if T was never, ToBoolean<T> is also never, and false wouldn't be assignable to it.

#

As for how to fix it, I can't really answer that because the code is honestly a bit weird and I don't know what the purpose is.

silver harbor
#

If I restrict T to extend only boolean, so it can't be never, the error still occurs:

type ToBoolean<T extends boolean> = T extends boolean ? boolean : T;

function f<T extends boolean>() {
  const x: ToBoolean<T> = false;
  return x;
}
finite stone
#

Yes, because never is the bottom type, it's assignable to everything.

silver harbor
#

This is full example that I have:

const defaults = {
  flag1: false,
  flag2: 'foo',
} as const;

type Defaults = typeof defaults;
type ToBoolean<T> = T extends boolean ? boolean : T;
type ValueType<T extends keyof Defaults> = ToBoolean<Defaults[T]>;

function getValue<T extends keyof Defaults>(key: T): ValueType<T> {
  // call to some third party fn that returns SettingTypeOf<T>
  return null as SettingTypeOf<ValueType<T>>;
}

// defined in third party lib:
type SettingTypeOf<T> = T extends boolean
? boolean
: T extends number
  ? number
  : T extends string
    ? string
    : T extends null
      ? boolean | number | string | null
      : T extends undefined
        ? boolean | number | string | undefined
        : any;
finite stone
#

So when you call getValue('flag2'), is it supposed to return 'foo' or some other string?

silver harbor
#
  • When I call getValue('flag2') it is supposed to get some string value with default as 'foo'
  • When I call getValue('flag1') it is supposed to get boolean value with default as false
finite stone
#

Yeah figure as much, but in your code getValue('flag2') would return 'foo' hence I asked.

silver harbor
#

Yeah, this is just a default value. Actual value retrieved from third-party function call

#

What I did now - I defined boolean default values with as boolean:

const defaults = {
  flag1: false as boolean,
  flag2: 'foo',
} as const;

This works but all these false as boolean look weird..

finite stone
#

I mean, you could just remove the as const.

void starBOT
#
const defaults = {
  flag1: false,
  flag2: 'foo',
}

defaults
// ^? - const defaults: {
//     flag1: boolean;
//     flag2: string;
// }
silver harbor
#

Yes, but in that case error still exists. I've created a playground

void starBOT
#
vitpotapov#0

Preview:```ts
const defaults = {
flag1: false,
flag2: "foo",
}

type Defaults = typeof defaults
type ToBoolean<T> = T extends boolean ? boolean : T
type ValueType<T extends keyof Defaults> = ToBoolean<
Defaults[T]

function getValue<T extends keyof Defaults>(
key: T
): ValueType<T>
...```

finite stone
#

I mean that if you don't use as const, you don't need ToBoolean<T> or SettingTypeOf<T> at all.

#

The return type will just simply be the same as typeof defaults[T].

#

But I don't know what role the third party lib plays in this snippet and if you can change it.

silver harbor
#

Yes, but without as const you don't control the shape of defaults object. I've switched to satisfies, but TS error still exists:

const defaults = {
  flag1: {
    defaultValue: false
  },
  flag2: {
    defaultValue: 'foo'
  },
} satisfies Record<string, { defaultValue: boolean | string }>;

type Defaults = typeof defaults;
type ToBoolean<T> = T extends boolean ? boolean : T;
type ValueType<T extends keyof Defaults> = ToBoolean<Defaults[T]['defaultValue']>;

function getValue<T extends keyof Defaults>(key: T): ValueType<T> {
  // call to some third party fn that returns SettingTypeOf<T>
  return null as SettingTypeOf<ValueType<T>>; // <-- ERROR still exists here!
}

// defined in third party lib:
type SettingTypeOf<T> = T extends boolean
  ? boolean
  : T extends number
    ? number
    : T extends string
      ? string
      : T extends null
        ? boolean | number | string | null
        : T extends undefined
          ? boolean | number | string | undefined
          : any;
finite stone
#

you don't control the shape of defaults object
What kind of control do you need?

#

There is a lot of information missing to help you, you should share as much relevant information as you can rather than us going back and forth and ask you to provide information piece by piece.

#

By this point I still have no idea what the role of third party lib is, if you can modify it, how it connects to your defaults, why those ToBoolean and ValueType need to exist, etc.

silver harbor
#

What kind of control do you need?
I need to control, that every item in defaults has defaultValue property and not defaultvalue or default

what the role of third party lib
To return value by provided key

if you can modify it
Definitely I can't modify it b/c it's third party lib

how it connects to your defaults
If third party can't return value, I should use value from defaults

why those ToBoolean and ValueType need to exist
Because for flag1 TypeScript infers type as false but it obviously should be boolean

finite stone
#

I'd say you should create a TS playground with your actual code (including the implementation of getValue), as well as the types of that third party library (you can import from npm if it's published there), and use comments to indicate which parts you can't change, what you are trying to achieve (with test cases)

void starBOT
#
vitpotapov#0

Preview:```ts
type Flag = {
defaultValue: boolean | string
}

const defaults = {
flag1: {
defaultValue: false
},
flag2: {
defaultValue: 'foo'
},
} satisfies Record<string, Flag>;

type Defaults = typeof defaults;
type ToBoolean<T> = T extends boolean ? boolean : T;
...```

finite stone
silver harbor
#

You are right! Updated code:

void starBOT
#
vitpotapov#0

Preview:```ts
type Flag = {
defaultValue: boolean | string
}

const defaults = {
flag1: {
defaultValue: false
},
flag2: {
defaultValue: 'foo'
},
} satisfies Record<string, Flag>;

type Defaults = typeof defaults;
type ToBoolean<T> = T extends boolean ? boolean : T;
...```

void starBOT
#
nonspicyburrito#0

Preview:```ts
const defaults = {
flag1: {
defaultValue: false,
},
flag2: {
defaultValue: "foo",
},
}

type Defaults = typeof defaults
type ValueType<T extends keyof Defaults> =
Defaults[T]["defaultValue"]

function getValue<T extends keyof Defaults>(
key: T
): ValueType<T> {
const d
...```

finite stone
#

@silver harbor ^

#

Your || defaultValue might also be wrong, you perhaps want ?? instead.

silver harbor
#

Your || defaultValue might also be wrong, you perhaps want ?? instead.
Yeap. Is it important for the typing error?

finite stone
#

No that's unrelated.

#

The code I gave you uses || as well and the types also work.

silver harbor
#

Cool, thank you! Let me check

#

@finite stone in your code you've just removed satisfies Record<string, Flag>; . This is not acceptable - I need to restrict the shape of defaults object.

finite stone
#

What do you want to preserve?

#

It's basically useless hence I removed it.

#

If all you want to preserve is the defaults[key].defaultValue structure, you can just change Flag to:

type Flag = {
    defaultValue: unknown
}
#

Alternatively, your current infrastructure is "infer the data types from the defaults" but imo it's better if you do the opposite by declaring the data types and then providing defaults.

void starBOT
#
nonspicyburrito#0

Preview:```ts
type Config = {
flag1: boolean
flag2: string
}

const defaults: {
[K in keyof Config]: {defaultValue: Config[K]}
} = {
flag1: {
defaultValue: false,
},
flag2: {
defaultValue: "foo",
},
}

function getValue<T extends keyof Config>(key: T) {
const d
...```

finite stone
#

I feel like the entire SettingTypeOf<T> is completely useless and stems from "I want to get the type by looking at default value" when in reality you really should do the opposite, by defining the type and providing default according to that type.

silver harbor
#

In general I agree, typing should go first to define the contracts. The intent was to keep things simple and not duplicate flag names, hoping TS will infer type by default values. As every flag is required to have a default value. Without third-party SettingTypeOf<T> the approach works fine, and I've tried to proceed with it. Thank you for your patience, anyway 🥰

void starBOT
#
nonspicyburrito#0

Preview:```ts
type Flag = {
defaultValue: unknown
}

const defaults = {
flag1: {
defaultValue: false,
},
flag2: {
defaultValue: 'foo',
},
} satisfies Record<string, Flag>

type Defaults = typeof defaults
type ValueType<T extends keyof Defaults> = Defaults[T]['defaultValue']
...```

silver harbor
finite stone
#

boolean in TS is true | false, so satisfies probably just infers too narrowly

#

unknown prevents that from happening.

#

Alternatively you can keep boolean | string but do defaultValue: false as boolean but eh, that's bad.