#Why does my conditional return type always have undefined or unknown values?

60 messages · Page 1 of 1 (latest)

glad violet
#
// sanity checks
type unknownExtendsUnknown = unknown extends unknown ? true : false;
//   ^?
type undefinedExtendsUnknown = undefined extends unknown ? true : false;
//   ^?
type unknownExtendsUndefined = unknown extends undefined ? true : false;
//   ^?

export const keyValueTypes = <T extends PropertyDescriptorMap>(
  obj: T
): // prettier-ignore
{
  [Key in keyof T]: 
  /* if */ unknown extends T[Key]['value'] ? /* then return */ T[Key]['value'] : 
  /* else if */ T[Key]['get'] extends (...any: any[]) => any ? /* then return */ ReturnType<T[Key]['get']> :
  /* else if */ T[Key]['set'] extends (...any: any[]) => any ? /* then return */ Parameters<T[Key]['set']>[0] : 
  /* else return */ undefined
} => {
  return {} as any;
};

const fn = () => 'hi';
const fn2 = (val: string) => {};
const x = keyValueTypes( {
  //  ^?
  // WHY undefined and unknowns only????
  v: { value: fn },
  v2: { value: 5 },
  getFn: { get: fn },
  getFn2: { get: fn, set: fn2 },
});

#

I don't know what I'm doing wrong here. I've simplified it to the point where looks straightforward to me. Why is this returning unknowns and undefined values?

#

!pg

#

!pg

chilly orbitBOT
glad violet
#

This works perfectly: I don't understand!

#
// sanity checks
type unknownExtendsUnknown = unknown extends unknown ? true : false;
//   ^?
type undefinedExtendsUnknown = undefined extends unknown ? true : false;
//   ^?
type unknownExtendsUndefined = unknown extends undefined ? true : false;
//   ^?
type stringExtendsUndefined = string extends undefined ? true : false;
//   ^?
type stringExtendsUnknown = string extends unknown ? true : false;
//   ^?
type unknownExtendsString = unknown extends string ? true : false;
//   ^?

export const keyValueTypes = <T extends PropertyDescriptorMap>(
  obj: T
): // prettier-ignore
{
  [Key in keyof T]: 
  /* if */ T[Key]['get'] extends (...any: any[]) => any ? /* then return */ ReturnType<T[Key]['get']> :
  /* else if */ T[Key]['set'] extends (...any: any[]) => any ? /* then return */ Parameters<T[Key]['set']>[0] : 
  /* else return */ T[Key]['value']
} => {
  return {} as any;
};

const fn = () => 'hi';
const fn2 = (val: string) => {};
const x = keyValueTypes( {
  //  ^?
  v: { value: fn },
  v2: { value: 5 },
  getFn: { get: fn },
  getFn2: { get: fn, set: fn2 },
});

#

!pg

chilly orbitBOT
glad violet
#

all I did was move the first condition to the bottom

solemn thicket
#

Your first condition is unknown extends T[Key]['value']

#

This can never be true unless T[Key]['value'] is unknown.

#

So v and v2 both fails the check, and fails the subsequent getter and setter checks, ending in undefined.

#

For getFn and getFn2, they don't have value so T[Key]['value'] is unknown (you can verify this by changing the map type to always return T[Key]['value']), so they satisfy the first check and immediately return T[Key]['value'], which is unknown.

glad violet
#

oh, maybe that doesn't make sense? These are type conditions, not value conditions?

solemn thicket
#

Eh yeah you are operating on the type level.

#

The definition is:

interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}

interface PropertyDescriptorMap {
    [key: PropertyKey]: PropertyDescriptor;
}
#

So I guess there's no way to distinguish missing value vs value is just unknown, since both cases they return unknown.

#

You second solution makes sense though.

glad violet
#

I went through an iteration that made absolutely no sense to me but seemed to work. Let me see if I can find it

solemn thicket
#

Oh

#

Duh, you can do this:

T[Key] extends { value: infer P } ? P : never
chilly orbitBOT
#
bawdyinkslinger#0

Preview:ts // sanity checks type unknownExtendsUnknown = unknown extends unknown ? true : false; // ^? type undefinedExtendsUnknown = undefined extends unknown ? true : false; // ^? type unknownExtendsUndefined = unknown extends undefined ? true : false; // ^? type stringExtendsUndefined = string extends undefined ? true : false; ...

glad violet
#

that type doesn't error locally, may be a tsconfig setting

solemn thicket
#

Yeah that ensures value actually exists, and infer is just to pull out the value type.

#

You could also write it as:

T[Key] extends { value: unknown } ? T[Key]['value'] : never

But there's no point in avoiding infer.

glad violet
# chilly orbit

so why does this appear to work? The longer I look at it, the more it feels like I need to negate the conditions

solemn thicket
#

This can never be true unless T[Key]['value'] is unknown, in the cases of value is missing (or is actually unknown), it is true so it proceeds to the branches that check for getter and setter.

#

And that also highlights an issue with it, that it cannot properly handle value of unknown.

glad violet
solemn thicket
#

It doesn't.

#

Try changing to v2: { value: 5 as unknown }.

glad violet
#

ok

#

ah

solemn thicket
#

When value is missing, it gives back unknown, so unknown extends T[Key]['value'] passes, and your code goes on to check for getter and setter.

#

But "value is missing -> gives unknown" is not something you should rely on.

glad violet
solemn thicket
#

Which part?

#

The "value is missing -> gives unknown" part?

glad violet
#

yeah

solemn thicket
#

Yeah I didn't know about it either 🤷

#

Best to not use it.

glad violet
#

you always returned T[Key]['value']?

#

no conditional?

solemn thicket
#

Yeah just some basic debugging

#

If you don't know why unknown extends T[Key]['value'] is passing when it shouldn't, easiest is to just change it to T[Key]['value'] and see what they are.

#

And by doing that you will find out missing value is treated as unknown.

glad violet
solemn thicket
#

Yeah, easiest think about how you would console.log things, and just do it on the type level.

glad violet
solemn thicket
#

Definitely don't disagree, debugging types is not the nicest thing.

#

Especially hover information sometimes is very unhelpful.

glad violet
#

Thank you, @solemn thicket