#why can I pass invalid object keys to a function with generic constraints?

1 messages · Page 1 of 1 (latest)

severe tulip
sick widgetBOT
#

@severe tulip Here's a shortened URL of your playground link! You can remove the full link from your message.

yoggyd#0

Preview:```ts
function fn<T extends Record<foo-${string}, number>>(
value: T
) {}

fn({foo: 0})```

short glade
#

Record<foo-${string}, number> says "all properties with key foo-${string} must have a value of number" it doesn't forbid other keys.

severe tulip
#
const foo: Record<`foo-${string}`, number> = { foo: 0 }

correctly complains though

#

ah... I see the problem. Yeah, you're right... any idea on how to forbid other keys in that position?

quaint vine
#

put the annotation on the parameter like you're doing there, rather than on an extensible generic

severe tulip
#

true... the problem is I need the exact structure for the return value

short glade
#

You can use a validator type to forbid other keys, but even then it can still be bypassed.

#

TS as a language does not care about excess properties.

severe tulip
#

yeah... damn. What do you mean by "validator type"?

sick widgetBOT
#
that_guy977#0

Preview:```ts
type OnlyFooKeys<T> = keyof T extends foo-${string}
? T
: never

function fn<T extends Record<foo-${string}, number>>(
value: OnlyFooKeys<T>
) {}

fn({foo: 0})
fn({"foo-": 0})
fn({"foo-": 0, foo: 0})```

short glade
#

^ That's a validator type.

quaint vine
#

could also replace the never with an error message that's disjoint with the constraint (which string is)

short glade
#

Although it can still be bypassed, so it's best not to rely on it.

severe tulip
#

ah, didn't know that was called a "validator type"

quaint vine
#

bypass:

sick widgetBOT
#
that_guy977#0

Preview:ts ... const x = { foo: 0, "foo-": 0 } const y: { "foo-": number } = x fn(y) // `foo` is still passed ...

severe tulip
#

yeah well that bypass is borderline malicious intent... if I explicitly omit the "invalid" property that's the devs my fault

quaint vine
quaint vine
severe tulip
#

explicitly changing the type so it can be passed to a function that wouldn't accept the previous type doesn't have to be "malicious"? (Sorry if malicious is the wrong word here, english isn't my mothers tongue. I mean "bad intent", similiar to arguing in bad faith)

sick widgetBOT
#
that_guy977#0

Preview:ts ... declare const x: Record<`foo-${string}`, number> // unknown extraneous keys const y = x as FooWithKeys<"a" | "b">; // intent: these keys are known // side effect: non-'foo-' keys are discarded from the type fn(y) // extraneous keys are still passed ...

quaint vine
#

here, it's a mistake, rather than intentionally getting around the validator type

short glade
#

It doesn't have to be explicitly changing the type either, it could just be unintentional like:

function change(arg: { 'foo-': string }) {
    return { ...arg, 'foo-': arg['foo-'].length }
}

const x = { foo: 'bar', 'foo-': 'baz' }
const y = change(x)
severe tulip
short glade
#

change is not the function doing the validation

#

That example is showing that x could be changed into y and lose the type for foo unintentionally, so when you later pass y into your fn that checks for invalid keys, it would pass.