#How can my function type automatically infer a type corresponding to one of its parameters?

1 messages · Page 1 of 1 (latest)

silk walrusBOT
#
thefrenchpoulp#0

Preview:ts ... // @ts-expect-error The key does not exist. translations.doesnotexist; ...

merry idol
#

I've used a declare statement on purpose because I'm trying to overwrite translate's type which comes from the import

junior monolith
#

i think i see what you're trying to do, but it seems a bit odd to expect the caller to check their own code via a type parameter of your function like that, since the type parameter isn't actually necessary for translate's signature. is there a specific reason it needs to be done this way?

#

my first instinct would be for call sites to just store id in a variable and reuse it across all of the translation objects, then it's guaranteed to be consistent

merry idol
#

I'm trying to enforce the ID syntax for translation locales as the developer declares them

#

In order to prevent typos and locales that are not declared in the right place
While also allowing for auto-completion when you import translations

junior monolith
#

well you're accepting any string, so it doesn't really prevent typos

merry idol
#

It should be enforced with the first generic Prefix, at least that was the plan

junior monolith
#

even if it were possible to partially-infer type parameter lists like you wanted (it isn't, BTW. TS doesn't have this feature), what's to stop someone from writing translate<string>(...)?

#

or not writing anything at all there and letting it be inferred as whatever

#

do you know what all the possible prefixes are at compile time? if so you could be stricter here

merry idol
#

Then the id would have to fit in ${string}.${string} I suppose, which is fine although I don't think there's a valid reason to declare any locale like that
The end goal is not to be fully error-proof, but rather export a const from domain/subdomain/component/translations.ts and ensure all locales contained use the right prefix domain.subdomain.component.*

junior monolith
junior monolith
silk walrusBOT
#
mkantor#0

Preview:```ts
...
const id = "domain.subdomain.open"

const translations = translate([
{
id,
key: "open",
},
{
id,
key: "close",
},
])
...```

junior monolith
#

which is just as safe (but IMO easier to review) than what you were going for (since like i said translate<string>(...) would have been totally valid)

#

there might be a way to write a validator type that accepts only string literals, but it's going to be weird/complicated and not bulletproof in any case. but if you think you really need that let me know and i'll play around

#

actually, here's another idea: change the signature of translate to accept id once as a separate parameter from the keys. then it's literally impossible to have more than one value 😄

merry idol
#

While I read your playground, I was retrieving the first version I had, which doesn't enforce much https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgCIRqYZgHsQA8A0hAJ7IQAekIAJgM7L1hSgDmyAvEy+wHzIA3gChkyYLQBcyEqQDco5AGsyAfmmyFAX2HDaEBABs4UFDACuIBDnzIWcEPWORiZCtQh1GzViDZ8ACkV9TBBsPEdpdFDw-FdSPgBtAF0AGmEASmkAJQNcKFp41J5ffwVhBHxmOygHJzgbRy4auucIAMTFETExCWkAclpcAFs4UAA6enMAIyHRidwAB09+9J7lMgGllbXkLV3unr7kQZGxkEmZufPxo1x6CFXFMRVSAbuHp7F94WSM8vsjmcEXoCgA9GDkAA9VS6QH1Rr0W6Ge4QcGQ9aY6Gw4QQ5AAATA9AAtFRltZSVAoPlkAAVAAWKFeyCGEEYIFwYHcwGY42E8OBVXGrPoHMglB5YHRWMxMOEQA
Which doesn't enforce the prefix but does provide autocompletion based on the input array
With my first playground, I was trying to 1. add the prefix rule 2. remove the id from the autocomplation (see the infered type for the translations const)

silk walrusBOT
#

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

thefrenchpoulp#0

Preview:```ts
interface Definition<Key extends string = string> {
id: Key
key?: Key
}

declare function translate<Key extends string>(
definitions: Definition<Key>[]
): Record<Key, string>

const translations = translate([
{
id: "domain.subdomain.open",
key: "open"
...```

junior monolith
silk walrusBOT
#
mkantor#0

Preview:```ts
...
declare function translate<Key extends string>(
id: ${string}.${string},
definitions: readonly Key[]
): Record<Key, string>

const translations = translate("domain.subdomain", [
"open",
"close",
])
...```

merry idol
#

Yeah I was coming to it, I can't change translate implementation unfortunately

junior monolith
#

ah, too bad

merry idol
# silk walrus

Basically yes that works, except I'd like the prefix declared with the type and not from a value

#

Because ${string}.${string} is too broad and only checks that there are 2 segments essentially, whereas I'd like to set that prefix for each translate call

#

What I'm not getting is how can TypeScript infer Key here,

declare function translate<Key extends string>(
  definitions: Definition<`${string}.${string}`, Key>[],
): Record<Key, string>;

But couldn't infer it when I tried pulling ${string}.${string} from the translate generic with my first proposal

junior monolith
#

you were trying to explicitly specify a single type parameter for a function that requires two type parameters

#

that is simply not a thing you can do in TS

#

either all type parameters must have explicit concrete values (default values count) or all are inferred. there's no way to say "infer type parameter 2 but not type parameter 1"

#

you can kinda fake it by splitting your function up, like instead of <A, B>() => Z do <A>() => <B>() => Z, but i don't know that it really gets you anything since like i said it's still up to callers to check their own ids, which they can do on their own without your function needing a fancy signature

merry idol
#

I think I can loosen restrictions for the IDs then
As long the output is not a dumb Record<string, string> that's fine I think
The issue now becomes that translate in its implementation has key as optional and when omitted, the output uses id as key in the dictionary of locales

junior monolith
#

maybe you can make key required in your stricter type signature?

merry idol
#

That'll make key required in the whole code base right?
The declare statement is in my current file where I can be stricter with the declarations but the rest of the code base shouldn't have type errors (yet)

#
silk walrusBOT
#

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

thefrenchpoulp#0

Preview:```ts
interface Definition<Key extends string = string> {
id: Key
key?: Key
}

declare function translate<Key extends string>(
definitions: Definition<Key>[]
): Record<Key, string>

const translations = translate([
{
id: "domain.subdomain.open",
key: "open"
...```

merry idol
#

The issue is that translations if you hover over it has both open and domain.subdomain.open as valid indexes but shouldn't have the latter

junior monolith
junior monolith
# junior monolith you can kinda fake it by splitting your function up, like instead of `<A, B>() =...

i was thinking about this approach some more and threw this together: https://tsplay.dev/wg1L9m. that still doesn't go out of its way to prevent translate<string>()(...) and whatnot (but i could add that if you really need it). i still suspect it's not really worth it though, and since you said you have to use the existing translate signature then that extra call is probably not going to work anyway

junior monolith
junior monolith
merry idol
junior monolith
#

ah, i must have been confused about what you meant by "The declare statement is in my current file where I can be stricter with the declarations"

junior monolith
merry idol
#

If not, I'm not sure how to write that, when I'm importing translate already

junior monolith
#

if you're trying to just override the type without changing the runtime value, you can't. the first call to translate() would fail, or at least return a value that's not a function so the second call would fail

merry idol
#

Right

junior monolith
merry idol
#

I'm not sure why I haven't tried that already tbh

#

Sounds like the easiest, although more code to maintain
Good call

junior monolith
merry idol
#

That's fair

#

When does the thread automatically close? I'll probably be able to finish working on that on Monday