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

Derive boilerplate type guards for unknown types #26078

Closed
mikeplus64 opened this issue Jul 31, 2018 · 8 comments
Closed

Derive boilerplate type guards for unknown types #26078

mikeplus64 opened this issue Jul 31, 2018 · 8 comments
Labels
Duplicate An existing issue was already created

Comments

@mikeplus64
Copy link

mikeplus64 commented Jul 31, 2018

Search Terms

validate
reflect
assert
unknown

Suggestion

TypeScript 3 adds an unknown type that must be inspected to be (safely) cast to another type. This manual type-checking code however is just boilerplate; it's easy to get wrong, easy to miss parts, and IMO adds absolutely no value for the programmer to have to type themselves. This boilerplate should be derivable for any non-function type, and several projects exist already that do this (or more); e.g., flow-runtime and io-ts.

This is a limited version of #1573 which suggested blanket runtime type-checking. I am suggesting a builtin operator like as that takes x: unknown as its first argument, a type T as its second, and produces x is T. Call this operator is, maybe.

Also desirable would be for error messages documenting why an unknown value does not validate; e.g. val does not have fields { x: number, y: number }. So the second part of this proposal is for a separate operator is! that is identical to is but throws a TypeError upon a type error.

I think that now that there is an unknown type, where its only use is for users to write mechanically derivable boilerplate to cast it to a known type, this is the logical next step.

I do not believe this is in opposition to the non-goal of adding/relying on runtime type-information. This only adds an extremely limited type reflection mechanism, motivated only by reducing boilerplate.

Use Cases

Any input data that's unknown; e.g. the output of a HTTP endpoint, or a websocket, some user input, JSON.parse, etc.

Examples

type Message
  = { type: 'ping', time: number }
  | { type: 'pong', time: number };

function onreceive(data: unknown) {
  if (data is Message) {
    ...
  }
}

should, roughly, generate

function onreceive(data) {
  if (typeof data === 'object' && data.type === 'ping' && typeof data.time === 'number' ||
      typeof data === 'object' && data.type === 'pong' && typeof data.time === 'number') {
    ...
  }
}
@mikeplus64
Copy link
Author

Related #26064 #7481 #10421

@AlCalzone
Copy link
Contributor

I think the syntax might be against the design goals of having "just JavaScript" + types. But I guess your proposal could be turned into a quick fix for auto-generating type guards, like this:

type Message // hover here to auto-generate a typeguard
  = { type: 'ping', time: number }
  | { type: 'pong', time: number };

// this will get generated
function isMessage(arg: any): arg is Message {
    return (typeof arg === 'object' && data.arg === 'ping' && typeof data.arg === 'number' ||
      typeof data === 'object' && data.arg === 'pong' && typeof data.arg === 'number');
}

// so you can use it with an unknown variable:
function onreceive(data: unknown) {
  if (isMessage(data)) {
    // ...
  }
}

@mhegazy
Copy link
Contributor

mhegazy commented Jul 31, 2018

As @AlCalzone noted, we do not intend to introduce new JS-expression syntax for type-only purposes. that would not be inline with our design goals.

Allowing some narrowing constructs to work on unknown to "prove" some aspects of the type is a valid option though. this is currently tracked by #25720, #10715, #25172, and #21732

@mhegazy mhegazy added the Duplicate An existing issue was already created label Jul 31, 2018
@mikeplus64
Copy link
Author

@mhegazy TypeScript already has e.g. enums that mechanically generates JavaScript code that is a chore to write manually, but provides immense value. Given how boring and easy-to-mess-up the type-checking code actually is, I think it's at the same level of "tolerable design goal exception". If the problem is just that it's an expression, how about a new statement a la enum:

type Message = { ... };
typeguard isMessage(arg: unknown): arg is Message;
// function isMessage(arg:unknown): arg is Message { ... }

I just don't see what value is provided in having to write this stuff manually. Being able for tooling to generate the boilerplate like @AlCalzone suggests would be a decent and probably much less controversial solution.

@mattmccutchen
Copy link
Contributor

For anyone finding this issue, this Stack Overflow thread has a few third-party tools that generate runtime checks based on interfaces. I don't know if any of the tools still work.

@bensalilijames
Copy link

It's worth checking out typescript-is, which is a transformer that implements is.

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@sgronblo
Copy link

sgronblo commented Oct 9, 2019

I also think this would be wonderful to have. This is one of the first problems you have to solve in most applications. TypeScript of course works wonderfully for type checking inside your own system. But in order to validate data coming in to your system you have to resort to using tools that either complicate your build process by adding some additional preprocessing step or using unofficial tools like typescript-is/ttypescript, or complicating the way you define your types such as with io-ts (which many times probably looks odd to many Typescript developers). Maybe the most likely approach is for the "custom transformers" to become more mature in Typescript?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

7 participants