Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type metadata / shadow types #46142

Open
5 tasks done
mckravchyk opened this issue Sep 30, 2021 · 1 comment
Open
5 tasks done

Type metadata / shadow types #46142

mckravchyk opened this issue Sep 30, 2021 · 1 comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@mckravchyk
Copy link

mckravchyk commented Sep 30, 2021

Suggestion

πŸ” Search Terms

type metadata info secondary

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Create a type that in a union, has no effect on the result of the union (same as never), but can be retrieved using a special type (i.e. GetMetadata<T>).

type x = number | Metadata<unknown>; // type is: number
type metaX = GetMetadata<x>; // type is unknown

πŸ“ƒ Motivating Example

This can be used to document errors which a function may throw.

Currently, Typescript allows to do something like the below, but there's an issue in that the Throws<> type pollutes the function return type which has to be manually eliminated by type casting - not very practical.

This is a full example of what I'm aiming at, from my comment in the issue #13219 (comment)

type Throws<T> = { throws: T };

type GetThrow<T> = T extends Throws<infer R> ? R : never;
type GetResult<T> = T extends Throws<unknown> ? never : T;

type GetFunctionThrow<T extends (...args: any[]) => any> = GetThrow<ReturnType<T>>;
type GetFunctionResult<T extends (...args: any[]) => any> = GetResult<ReturnType<T>>;

type CalcError = {
    code: "calc_error"
    message: string
}

type DivisionError = {
    code: 'division_error'
    message: string
}

function divide(a: number, b: number): number | Throws<DivisionError> {
    if (b === 0) {
        throw { code: "division_error", message: "Cannot divide by 0" } as DivisionError; 
    }

    return a / b;
}

function calc(a: number, b: number, operation: 'divide'): number | Throws<CalcError | DivisionError> {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw { code: "calc_error", message: "Required 2 number arguments" } as CalcError;
    }

    if (operation !== 'divide') {
        throw { code: "calc_error", message: "Unsupported operation" } as CalcError;
    }

    return divide(a,b);
}

let x: number;

try {
    // This casting makes this method hardly practical. If we had a metadata type, it would be a number by default.
    x = calc(5 ,6, 'divide') as GetFunctionResult<typeof calc>;
} catch (e: unknown) { // Catch-clause annotation must be any or unknown
    const err = e as GetFunctionThrow<typeof calc>; // CalcError | DivisionError
}

Playground

With the new special Metadata<T> utility type it would possible to store such information and access it with a special type GetMetadata<T> while the type like number | string | Metadata<unknown> would be just number | string.

πŸ’» Use Cases

  1. What do you want to use this for?
    Documenting what types of errors a function may throw.

  2. What shortcomings exist with current approaches?
    It's possible to use a helper type to eliminate the metadata from the union, but it's too verbose to be practical. It could also be stored in a separate type, but with classes, it would be away from the method body and also not practical enough to consider.

  3. What workarounds are you using in the meantime?
    I'm not documenting the types of errors a function may throw at all or I use tsdoc throws (but it does not allow to store type info).

@andrewbranch andrewbranch added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Oct 4, 2021
@mdbetancourt
Copy link

mdbetancourt commented Nov 7, 2021

i almost publish my lib https://github.com/kraftrio/kraftr/tree/develop/core/errors you can check it how i handled to store metadata, i created a eslint plugin too to enforce the usages (could have auto fix)
edit: i published my lib
you can try it here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants