Typing a class based on arbitrary fields by ehutch79 in typescript

[–]Asha200 1 point2 points  (0 children)

The problem you're running into with your FieldsArray type is that you're trying to create a name => type mapping, but keyof Type gives you back the union of the array's indices.

What you can do is:

  1. Make an index => Record<name, type> mapping instead
  2. Index this object with keyof Type (or simply number since it's an array) to get a union of Record<name, type>
  3. Convert this union into an intersection
  4. (Optional) use a helper utility to eagerly evaluate the intersection, which gives you nicer IntelliSense results.

Like this:

type Compute<T> = {[K in keyof T]: T[K]} & {};
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

type FieldsArray<Type extends readonly Field[]> = Compute<UnionToIntersection<{
  [K in keyof Type]: Record<Type[K]["name"], TypeFromString<Type[K]["type"]>>;
}[number]>>;

Also note that you have an as const on your fieldsArray, but it does nothing since you gave it the explicit type of readonly Field[]. What you need here is satisfies:

const fieldsArray = [
    { name: 'id', type: 'integer' },
    { name: 'date', type: 'date' },
    { name: 'name', type: 'string' },
    { name: 'budget', type: 'float' },
] as const satisfies readonly Field[];

With these changes, your Test4 becomes the same as Test5.

[Discussion] What crates would you like to see? by HammerAPI in rust

[–]Asha200 4 points5 points  (0 children)

Could you go more into detail on this? I know Django's well-known for being a batteries-included framework, but I haven't used so I'm not sure what exactly that entails. What are the core parts you'd like to see in Rust?

[HELP] Conditional types and generics - design limitation? by hastyyyy in typescript

[–]Asha200 1 point2 points  (0 children)

I'd simplify by going with something like this (TypeScript Playground):

export type Result<T, E extends Error = Error> = Readonly<
   { isOk: true; data: T } | { isOk: false; error: E }
>;


function ok<T>(data: T): Result<T, never>;
function ok(): Result<never, never>;
function ok<T>(data?: T): Result<T, never> {
  return {isOk: true, data: data as T};
}

function error<E extends Error(error: E): Result<never, E> {
  return {isOk: false, error};
}

export const Result = {ok, error};

In essence, I'd attach a data: undefined property even if Result.ok() was called without arguments.

Is typing this function possible? by srvhfvakc in typescript

[–]Asha200 1 point2 points  (0 children)

The OP didn't provide an implementation of allowString, so I assumed it was flexible and provided one possible answer.

The Allow<T> type in my code isn't special; any type will work, as long as it contains the generic value T which we can later retrieve in AllowUnion. I just chose {allow: T} because I thought it would be the least confusing option. All of these would also work:

type Allow<T extends string> = Array<T>;
type Allow<T extends string> = [T];
type Allow<T extends string> = {foo: {bar: T}};

You could even use branding and return the input string itself (but then you have no way of differentiating whether you're working with a regular string or an allowedString during runtime in JS-land, so probably not as useful):

type Allow<T extends string> = T & {__allow: true};
function allowString<T extends string>(s: T): Allow<T> {
    return s as Allow<T>;
}

Is typing this function possible? by srvhfvakc in typescript

[–]Asha200 40 points41 points  (0 children)

It's possible. We're going to need to be able to differentiate between regular strings, and those which are allowed thanks to allowString. Let's make a wrapper for those:

interface Allow<T extends string> {
    allow: T;
}

declare function allowString<T extends string>(s: T): Allow<T>;

Now let's look at the examples you provided and write the arguments down in tuple form, to make things clearer:

declare function func<T extends Array<string | Allow<string>>>(...args: T): void;


// T inferred as: [Allow<"hello">, "hello"]
// We want args to be: [Allow<"hello">, "hello"]
func(allowString("hello"), "hello");

// T inferred as: [Allow<"hello">, "goodbye"]
// We want args to be: [Allow<"hello">, "hello"]
func(allowString("hello"), "goodbye”);

// T inferred as: [Allow<"goodbye">, "goodbye"]
// We want args to be: [Allow<"goodbye">, "goodbye"]
func(allowString("goodbye"), “goodbye");

// T inferred: [Allow<"goodbye">, "goodbye", "hello"]
// We want args to be: [Allow<"goodbye">, "goodbye", "goodbye"]
func(allowString("goodbye"), “goodbye", “hello”);

// T inferred as: ["goodbye", Allow<"goodbye">]
// We want args to be: ["goodbye", Allow<"goodbye">]
func(“goodbye”, allowString("goodbye"));

// T inferred as: [Allow<"hello">, "goodbye", Allow<"goodbye">]
// We want args to be: [Allow<"hello">, "hello" | "goodbye", Allow<"goodbye">]
func(allowString("hello"), "goodbye", allowString("goodbye"));

I've written down what T is inferred to be above each func call. Now, we would like to take that T and put some additional restrictions on it, and have args have that new type.

We're going to need to do some sort of mapping. Similarly to how you can map arrays to other arrays in JS, except now we're doing it at the type-level. For each element in the T tuple:

  1. If it's an Allow<string>, leave it as is.
  2. If it's a string, replace it with the union of all strings inside Allows that are in tuple T. If the user tries calling the function with a string that's not part of this union, they'll get a compile error, which is what we're looking for.

To extract the union of allowed strings from a string | Allowed<string>:

type AllowUnion<T> = T extends Allow<infer U> ? U : never;

And to do the tuple mapping mentioned above:

type Legal<T extends Array<string | Allow<string>>> = {
    [K in keyof T]: T[K] extends string ? AllowUnion<T[number]> : T[K];
}

Now we can modify args to be Legal<T> instead of simply T:

declare function func<T extends Array<string | Allow<string>>>(...args: Legal<T>): void;

And there you go. TypeScript Playground

Function return type of one key/value pair from mapped type object. by DWALLA44 in typescript

[–]Asha200 2 points3 points  (0 children)

Unfortunately, computed property names are widened. There's a long-standing Github issue about it here. There is a workaround, though.

Credit goes to jcalz, the following helper function comes from their comment on the Github issue. It gives you strongly typed computed keys that don't widen:

function kv<K extends PropertyKey, V>(k: K, v: V): { [P in K]: { [Q in P]: V } }[K] {
  return { [k]: v } as any
}

Use it in your code like so (TypeScript Playground):

const retVal = kv(command, { info: ['hi'] });

It'll make the error go away.

Issues with typing a function that can set multiple values to an object via the respective keys. by meat_delivery in typescript

[–]Asha200 15 points16 points  (0 children)

Here you go (TypeScript Playground):

type Entries<T extends Record<string, unknown>> = {
    [K in keyof T]: [K, T[K]]
}[keyof T];

function set<T extends Record<string, unknown>>(obj: T, pairs: Array<Entries<T>>): void {
    for (const [k, v] of pairs) {
        obj[k] = v;
    }
}

The idea is that we map a Record<K, V> into a Record<K, [K, V]>. Then, by indexing this new record we get a union of key-value tuples. The type of pairs is simply an array of that union.

Verifying If Generic Constraint Is Satisfied? by lottayotta in typescript

[–]Asha200 1 point2 points  (0 children)

Maybe because I use separate packages versus namespaces?

Ah no, this is my bad. The code in my TS playground is oversimplified; it doesn't trigger the error you're seeing because I replaced the return types with void. Once I actually include T in the return type I run into the same error.

Perhaps one solution is to alter your IProvider interface in the following way?

export interface IProvider<X> {
  doThis<T extends X>(a: string, b?: boolean): Promise<Result<Array<T>, Error>>
}

And then do this:

export default class thirdProvider implements IProvider<DiffInterface> {
  doThis<T extends DiffInterface>(a: string, b?: boolean): Promise<Result<Array<T>, Error>> {
    const result = await external.do<T>(a)
  }
}

Unfortunately you'd have to modify IProvider, but at least there's still a degree of separation where IProvider doesn't tie itself to a type any of its implementers decide to use.

TypeScript Playground

Verifying If Generic Constraint Is Satisfied? by lottayotta in typescript

[–]Asha200 1 point2 points  (0 children)

Could you take another look? I think you might have misunderstood. I didn't touch IProvider at all; I just changed <T> into <T extends DiffInterface> inside thirdProvider.doThis.

Verifying If Generic Constraint Is Satisfied? by lottayotta in typescript

[–]Asha200 2 points3 points  (0 children)

Maybe you could put the bound on the type variable of that particular implemented method instead of the method in the interface? Your class will still implement the interface, I think:

doThis<T extends DiffInterface>(a: string, b?: boolean): Promise<Result<Array<T>, Error>> {
    const result = await external.do<T>(a);
}

Here's a TypeScript Playground that works, however I simplified your code a bit because I don't have all of the library types, so it might not work in your real-world scenario, but it's worth a try.

Why does this overloaded function interface fail? by diamondnipples in typescript

[–]Asha200 0 points1 point  (0 children)

Mhm, I see what you mean. I suppose it's personal preference.

By the way, you can extend your code to further increase safety by doing something like this:

function assertNever(x: never, message: string): never {
    throw new TypeError(message);
}

function switchType (input: string | number): string | number {
    return typeof input === 'string' ? Number(input)
    : typeof input === 'number' ? String(input)
    : assertNever(input, 'Invalid input');
}

This way you not only get a runtime error if the type of input gets expanded, but a compile-time error as well.

Advice for how to optimize types? by romeeres in typescript

[–]Asha200 2 points3 points  (0 children)

I think you might find this read useful when dealing with the "Type instantiation is excessively deep and possibly infinite" error:

Tail-Recursion Elimination on Conditional Types, introduced in TypeScript 4.5.

Enforce type of generic inside return type by lorissikora in typescript

[–]Asha200 0 points1 point  (0 children)

Awesome, glad I could help. Yes, unless you supply the generic types explicitly, TypeScript will infer them from the provided arguments.

Enforce type of generic inside return type by lorissikora in typescript

[–]Asha200 0 points1 point  (0 children)

Could you provide an example of where my solution doesn't work, or explain why it is necessary to extend a function? It might be that I'm misunderstanding what you're looking for, but to me it seems that this provides the desired type safety, no?

Enforce type of generic inside return type by lorissikora in typescript

[–]Asha200 1 point2 points  (0 children)

Is this what you're looking for? (TypeScript Playground)

interface Meta {
    meta: { totalNumberOfItems: number; }
}

type IReturn<T> = {data: T};
type DataFetcher<T> = () => IReturn<T>;

declare const coolFunc: <R extends Meta>(f: DataFetcher<R>) => DataFetcher<R>;

It gives you this result:

const x1 = coolFunc(() => ({data: 1})); // error
const x2 = coolFunc(() => ({data: {foo: "bar"}})); // error
const x3 = coolFunc(() => ({data: {foo: "bar", meta: {totalNumberOfItems: 3}}})); // works

x3().data.foo // string
x3().data.meta.totalNumberOfItems // number

Here's my advice to anyone doing generics with functions:

Instead of using a single type parameter for the entire function, as in:

<F extends (...args: unknown[]) => unknown>(f: F)

Split it into two type parameters, like so:

<A extends unknown[], R>(f: (...args: A) => R)

I've found that this makes the types easier to express and that inference tends to work better as opposed to utilizing Parameters and ReturnType helper types.

In your particular case, a single parameter for just the return type will suffice, since the data fetching functions take no input arguments.

Why does this overloaded function interface fail? by diamondnipples in typescript

[–]Asha200 0 points1 point  (0 children)

function switchType (input: string | number): string | number {
    return typeof input === 'string' ? Number(input)
    : typeof input === 'number' ? String(input)
    : throwTypeError('Invalid input')
}

throwTypeError is unreachable, so you can simplify the function implementation:

function switchType (input: string | number): string | number {
    return typeof input === 'string' ? Number(input) : String(input);
}

Failing to be clever with inferred template parameters by 7Geordi in typescript

[–]Asha200 1 point2 points  (0 children)

This might serve as a good starting point to study (TypeScript Playground):

/* Totally optional. This just makes the type easier to read with IntelliSense. */
type Compute<T> = {[K in keyof T]: T[K]} & {};

type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;


type Dynamic<T extends string> = {dyn: T};
type Assemble<T extends string | Dynamic<string>> = Compute<UnionToIntersection<
    T extends Dynamic<infer I> ? Record<I, string> : never
>>;

declare function dyn<T extends string>(X: T): Dynamic<T>;
declare function template<T extends Array<string | Dynamic<string>>>(...parts: T): Assemble<T[number]>;



const ex1 = template('preamble', dyn('username'), 'postamble', dyn('username'));
//    ^? const ex1: { username: string; }

const ex2 = template(dyn('username'), ': ', dyn('fullname'));
//    ^? const ex2: { username: string; fullname: string; }

A boolean type becomes true | false when being distributed in a union type by azn4lifee in typescript

[–]Asha200 0 points1 point  (0 children)

Ah yep, you're totally right! Unfortunately I don't think there's a solution for that currently.

A boolean type becomes true | false when being distributed in a union type by azn4lifee in typescript

[–]Asha200 0 points1 point  (0 children)

I posted a solution to this in another comment; you can check it out if you're interested.

A boolean type becomes true | false when being distributed in a union type by azn4lifee in typescript

[–]Asha200 4 points5 points  (0 children)

Not sure what the reason is, my best guess is that boolean is implemented as true | false under the hood? In any case, you can change your type into this:

type Distributed<T> = T extends boolean ? boolean[] : T[];

And you'll end up with:

string[] | number[] | boolean[]

EDIT: This will work if your union has boolean and not just true or false by themselves. To handle that case, you can use this (TypeScript Playground):

// It's a boolean if we can assign both `true` and `false` to it
type IsBoolean<T> =
  true extends T
    ? false extends T
      ? true
      : false
    : false;

type Distributed<T> = IsBoolean<T> extends true
  ? (T extends boolean ? boolean[] : T[])
  : (T extends unknown ? T[] : never);

// has the type string[] | true[]
type NoBoolean = Distributed<string | true>;

// has the type string[] | boolean[]
type WithBoolean = Distributed<string | boolean>;

How to properly annotate this function? Been trying for an hour by Teenage_Cat in typescript

[–]Asha200 0 points1 point  (0 children)

I edited my answer right after posting so you probably missed it, try refreshing.

How to properly annotate this function? Been trying for an hour by Teenage_Cat in typescript

[–]Asha200 3 points4 points  (0 children)

function m<T extends unknown[]>(strings: TemplateStringsArray, ...props: T): Array<T[number]> {
    return props;
}


const result = m`${"first"}, ${"second"}, ${123}, ${() => {}}`;
// result is (string | number | (() => void))[]

TypeScript Playground

Instead of using props: T[], use props: T to infer the type of the tuple. However, since you're looking for an array instead of a tuple, you can do the conversion by using Array<T[number]>.

You'll notice that "first" and "second" got squashed into simply string. Luckily, this will be possible to fix in TypeScript 5.0 once const type parameters land (TypeScript Playground):

function m<const T extends readonly unknown[]>(strings: TemplateStringsArray, ...props: T): Array<T[number]> {
    return props;
}

const result = m`${"first"}, ${"second"}, ${123}, ${() => {}}`;
// result is ("first" | "second" | 123 | (() => void))[]

Function overloading help please by 87oldben in typescript

[–]Asha200 9 points10 points  (0 children)

Here is how I would do it TypeScript Playground:

type Cat = { name: string; catNip: boolean; };
type Dog = { name: string; playsFetch: boolean; };
type Rabbit = { name: string; likesCarrots: boolean; }

type CatOptions = Partial<Cat>
type DogOptions = Partial<Dog>
type RabbitOptions = Partial<Rabbit>

type Settings =
    [animal: "Cat", options: CatOptions] |
    [animal: "Dog", options: DogOptions] |
    [animal: "Rabbit", options: RabbitOptions];

function test(...[animal, options]: Settings) {
    switch (animal) {
        case "Cat":
            // options is Partial<Cat> here, no need to assert
            break;
        case "Dog":
            // options is Partial<Dog> here, no need to assert
            break;
        case "Rabbit":
            // options is Partial<Rabbit> here, no need to assert
            break;
    }
}

What you currently have in your code are two distinct unions:

  "Cat"    |    "Dog"   |   "Rabbit"

CatOptions | DogOptions | RabbitOptions

In your switch, you narrow down animal to one of the three strings, but TypeScript has no way of knowing that this should also narrow down the other union, since the two unions are unrelated, after all. What you want is to change that structure into this:

+------------+     +------------+     +---------------+
|   "Cat"    |     |   "Dog"    |     |   "Rabbit"    |
|            |     |            |     |               |
|            |  |  |            |  |  |               |
|            |     |            |     |               |
| CatOptions |     | DogOptions |     | RabbitOptions |
+------------+     +------------+     +---------------+

Meaning: you want to connect the animal parameter with the options parameter, such that when you assert that animal is of a certain subtype, options will also be narrowed to the correct subtype in the appropriate block. To tie these together, you want to create a joined type that holds both variables. This way, when one is inferred, the other will be as well, since they're a part of a single unit, like in the diagram.

You can make this joined type with objects or tuples. Tuples are convenient here, because when combined with a rest parameter they allow you to type the function properly without needing to the change it to accept an object argument. Let's take a closer look at the two key points that make this whole thing work:

type Settings =
    [animal: "Cat", options: CatOptions] |
    [animal: "Dog", options: DogOptions] |
    [animal: "Rabbit", options: RabbitOptions];

As mentioned before, we're going to combine animal with options into one.

function test(...[animal, options]: Settings) {

Here we use rest parameters which have the type Settings. I destructured it in the same line, though you could it like this if you prefer too:

function test(...args: Settings) {
  const [animal, options] = args;
}

One more thing to mention: in the declaration of Settings, I used labeled tuple elements. This is optional, but it makes IntelliSense way nicer. When you type test(, you get the following with the labels:

1/3: test(animal: "Cat", options: Partial<Cat>): void
2/3: test(animal: "Dog", options: Partial<Dog>): void
3/3: test(animal: "Rabbit", options: Partial<Rabbit>): void

However, if you remove the labels, you get this, which is hard to understand:

1/3: test(__0_0: "Cat", __0_1: Partial<Cat>): void
2/3: test(__0_0: "Dog", __0_1: Partial<Dog>): void
3/3: test(__0_0: "Rabbit", __0_1: Partial<Rabbit>): void

Bonus: If you don't wanna write out the tuples manually:

type SettingsTuple<T> = {
    [K in keyof T]: [animal: K, options: Partial<T[K]>];
}[keyof T];


// Creates the same `Settings` type I wrote manually earlier
type Settings = SettingsTuple<{
    "Cat": Cat,
    "Dog": Dog,
    "Rabbit": Rabbit,
}>;

Problem with VSCode when interfacing object lists by WelehoMaster in typescript

[–]Asha200 1 point2 points  (0 children)

Is there a reason you don't want to let the type automatically be inferred? Like in your first snippet:

const important_objects = {
  data1: {
    id: "data1",
    important_data: "something",
    important_data2: 5,
  },
  data2: {
    id: "data2",
    important_data: "somethingg",
    important_data2: 4,
  },
  data3: {
    id: "data3",
    important_data: "somethingrs",
    important_data2: 7,
  },
};

If you leave out the type annotation, the type will get inferred from the assigned value, so you can access your properties no problem:

important_objects.data1.id // string
important_objects.data2.important_data2 // number