I request a runtime type checking system that perhaps looks something like this:
function square(x: number!) {
return x * x;
}
Where the !
tells the compiler to generate a runtime type check for a number, something akin to tcomb.js.
Of course, this gets much more complicated with interfaces, but you get the idea.
Perhaps something more akin to google AtScript
for consistency. Basically flag --rtts
will cause emited JS to have rtts
(runtime type system) checks inserted.
I'm suggesting, however, that you can optionally enable runtime type checking on some args and not others. Or, at least, skip type checking on private methods, optionally.
This remains outside our design goals (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)
For people like me coming here looking with high hopes, you might want to take a look at http://angular.github.io/assert/
Well, this can't be very complex to implement since you already have the infrastructure for type checking. At least you could expect to get injected type checking for primitive types when compiling with --debug, as mentioned in the other thread:
This has lead us to introduce type checks for all exported functions in TypeScript, although they really feel verbose in a typed language.
export function add(x: number, y: number): number {
if (typeof x !== 'number' || typeof y !== 'number') {
throw new TypeError('x and y need to be numbers');
}
return x + y;
}
If this does not match your design goals, maybe you should change them?
Primitive TypeChecking is easy, and the results of doing it yourself guide type inference. If you expect to receive something other than the indicated type, perhaps you should not specify number
.
I would expect to get a runtime error if a function receives eg a string instead of a number in runtime. That would really kill the flaws of working with JavaScript.
Don't get me wrong, I love TypeScript, but I really hate JavaScript..
But, IMO, you need to learn to love JavaScript to fully take advantage of TypeScript.
@jeansson did you take a look at tcomb?
Well I think that is mission impossible for me. JavaScript is great for what is was made for, but for large complex systems built and maintained by a team I can't see how it could be a good modern option.
@jedmao Thank you, will have a look. But I still think it should be implemented in the TypeScript compiler (with a flag)
@RyanCavanaugh it's been almost 2 years since this feature request was made and I'm wondering what it would take for the design goals to change or make an exception for this particular request? Now that the initial TypeScript roadmap is complete, perhaps we can entertain this idea?
I think this proposed compiler flag would only make sense in development and staging environments, but it would serve as a great tool for debugging the application at runtime.
Consider the scenario in which an API request is made and we expect the API response to adhere to a model (interface).
interface Foo {
bar: string;
}
function handleResponse(data: Foo) {
return data;
}
Compiles to:
const __Foo = {
validate: (d) => {
if (typeof d.bar !== 'string') {
throw new TypeError('Foo.bar should be a string.');
}
}
};
function handleResponse(data) {
Foo.validate(data);
return data;
}
handleResponse({ bar: 'baz' }); // OK
handleResponse({ bar: 42 }); // TypeError('Foo.bar should be a string.');
Rather than getting some error about some foo
prop being undefined, wouldn't it be more useful to know that the complex type that was defined as input didn't come back the way we expected?
@jedmao
+1 on that. Of course this is only relevant during development and testing, at least to begin with, because I guess it adds up to compile time and also adds runtime overhead. I recently encountered a situation where this would have saved a lot of time where an API returned a string inside a json-object where a number was expected. That resulted to weird runtime behavior that would be very easy to spot if I could compile with a --runtimeErrors flag.
I think this a natural next step for TypeScript
@jedmao @jeansson If it is string
vs number
you can already do what you want, but you have to use classes because you have to use decorators, but it can be done.
model.ts
import reified from './reified';
export default class Model {
@reified bar: string;
@reified baz: number;
constructor(properties: Partial<Model> = {}) {
Object.assign(this, properties);
}
}
const y = new Model({
baz: '42' as any, // throws at runtime
bar: 'hello'
});
reified.ts
import 'reflect-metadata';
export default function <T extends Object>(target: T, key: string) {
const fieldKey = `${key}_`;
console.log(target[fieldKey]);
const fieldTypeValue = getTypeTestString(Reflect.getMetadata('design:type', target, key));
Object.defineProperties(target, {
[key]: {
get() {
console.log(`${key}: ${fieldTypeValue} = ${this[fieldKey]}`);
return this[fieldKey];
},
set(value) {
if (fieldTypeValue && typeof value !== fieldTypeValue) {
throw TypeError(`${fieldTypeValue} is incompatable with ${typeof value}`);
}
this[fieldKey] = value;
}, enumerable: true, configurable: true
}
});
}
function getTypeTestString(type) {
switch (type) {
case Number: return 'number';
case String: return 'string';
default: return undefined;
}
}
Obviously this doesn't work for complex types.
@aluanhaddad, your solution would add footprint to the compiled output. The idea here is that it would be a compiler flag you could turn off for production builds and that it would work with complex types, not just classes.
I am not recommending it as a solution.
@aluanhaddad The whole point is that I should not be forced to check this manually in a typed language, so the type checking needs to be injected automatically at compile time if the flag is set. It also prevents the overhead @jedmao mentioned because the runtime type checking will not be injected when you build the release version.
Why do you not like this solution?
Look https://github.com/codemix/babel-plugin-typecheck. It could work like this but automatically.
Edit: found same tip: https://github.com/Microsoft/TypeScript/issues/7607#issuecomment-199227135
Rather than building runtime type checking into the compiler - an alternative might be to expose type metadata to runtime code (e.g. via Reflect.getMetadata
or something along those lines); probably only for reachable types. We can then build type checking libraries on top of that
This lib came across my slack this morning - although designed for Facebook's flow it states it could work with Typescript. It is built upon the work of the babel-plugin-typecheck that @GulinSS mentioned.
I can't understand why this is out of the scope of typescript, which adds exactly this kind of things to JS.
Seems that it would be a better idea to use flow + flow-runtime instead. What a pity because I like typescript.
I just did a proof of concept using TypeScript.
Only enforcing type checking at compile time and not run-time in JavaScript is dangerous.
You are essentially tricking developers into thinking they are writing Type safe JavaScript, when in fact, the moment any data from an outside system ( like the client ) comes into a "Typed" function you are going to be NaN
and can't read property length of undefined
errors all day long.
I've made a library for doing this sort of thing: https://github.com/pelotom/runtypes
It allows you to build composable validators which can be used to coerce untyped values to typed ones in a type-safe manner. It has the nice property that you don't need to repeat your type specification at the type and value level; simply specify the runtime validator, and make a type alias to the inferred type of the thing it validates.
It doesn't support function validation yet but I'm certainly open to adding that functionality.
@pelotom - Good to see. Thank you for posting.
Is there any adoption of this kind of technique ( runtypes
library )? We are evaluating the usage of TypeScript at scale for a large enterprise. Your library looks neat, but it seems to not have much of a following?
As for function validation, I'd probably make that a separate module.
@Marak: I literally just published it today, so that might explain lack of adoption ;) I'd love to get people using it which is why I posted here.
I will say it is very well tested and I have been using it heavily for some time as an internal library in a private company project.
@aluanhaddad
Primitive TypeChecking is easy, and the results of doing it yourself guide type inference. If you expect to receive something other than the indicated type, perhaps you should not specify number.
This is not the case. A library author making a library, for example a Transform class that has API like transform.translate(30,20,40)
might simply want to be helpful to any and all programmers, even if they are brand new to JavaScript. Throwing a helpful error (in this case when .translate
doesn't receive a number
) not only saves end users time in figuring out what the problem is, but can also help them learn.
The problem here isn't about accepting other things beside numbers or not, the problem is about making runtime errors easy to work with and easy to learn from, and to improve the end-dev experience. RequireJS and AngularJS are examples of libraries with very helpful runtime errors that link you up to specific documentation pages.
Furthermore, the consumer application of a library written in TypeScript may not have type checking at all. It can be beneficial to provide runtime errors for those consumer apps or libraries.
By arguing strictly against runtime type checks, you're arguing for the absolute possibility for non-typescript runtime consumers of any given library to be open to complete breakage, which defeats the whole purpose of writing your library for the _benefit of your end user_.
Just saying. Runtime checks can be really nice, and it can definitely be redundant to write the runtime checks when they are already written in TypeScript (in cases where the type check is deemed important for a library author's end user).
I'm not arguing against argument validation, including any runtime type checking deemed appropriate, I'm arguing against the idea that TypeScript should automatically synthesize these checks.
For simple types it's trivial to write these checks yourself and for complex types, which includes almost all TypeScript types, it's simply not feasible.
@gcanti cool, sounds like we have similar goals :)
For simple types it's trivial to write these checks yourself
@aluanhaddad
Yes of course, and then we can stop using arrow functions, and come back to the prototype.method thing, forget about the rest parameters because, hey, isn't Array.prototype.slice.call(parameters,3) simple enough?
If you say that Typescript should not do it because it is simple enough doing it yourself then the entire syntax-sugar thing makes no sense.
If you have philosophical reasons to not include it into TS then it is fine, at the end it is a matter of opinion, but don't say that it does not makes sense just because it does not fit your expectations/ideas.
My point is that for a bread and butter type like
(x: number) => "Hello"
there is no way to generate a meaningful runtime type check.
If all you want is checking primitive types then yes it can work but it is of extremely limited value and I think it will run out of mileage very quickly.
It is not just a philosophical difference, as it is a non-trivial problem.
I think this issue really should be properly considered again now we have strict null checks.
When it comes to dependencies (even node/browser apis) it's in declarations file we trust. One little mistake in these user contributed files and our compile time checks only help to mask a bug.
Im suprised this isnt a design goal - I wonder if such a flag existed how many codebases would start breaking due to an ill informed typesystem. I can imagine quite a lot, especially around nullability - I'm always finding errors in type definitions and without such a feature its just pot luck is has some kind of noticeable effect.
I would add that type checking at runtime sounds great in principle, but there are pros and cons. TINSTAA - a recent project I worked on used runtime flow checking. However we ran into issues with it and unfortunately ended up stripping it out. Ultimately it cost us more than it gave us.
Bare in mind it is going to change the code you write (no different from compilers and TS of course) and is dependent on the maintainers (who I have huge support for in the OSC); even type definitions can be wrong - think of the ever changing JS lib that is in production before v1!
It would be great in development but needs to be backed by a team who can handled the (huge) number of edge /variations in JS/TS. Ultimately error handling has to written in by the developer regardless of if its a lib or the browser that has found your bug / bad data.
Since this seems to be an hot topic and because internet seems to forget, I'd like to remind everyone that, 3 years ago, this feature has been implemented as a research project. It seems on the TypeScript team radar according to this.
Why that work wasn't merged yet, as @RyanCavanaugh, already stated: It's not in the TypeScript Design Goals
If that doesn't satisfy you, here's a few more reasons:
false
.@aluanhaddad, I love TypeScript as well as JavaScript and I would love to see TypeScript continue to dominate alternatives at every corner! If we could figure out a solution to the run-time type checking w/o duplication of type information, I really think it would provide a value in catching more errors before they reach production environments.
@jedmao In an perfect world, yes we could have a type system that was 100% reified and at the same time fully transparent.
If it were 100% fidelity, I would be all for it because it would be just a consequence of using the language.
So I am not opposed to that ideal. What I am opposed to is trying to take 20% of TypeScript's expressiveness and reifying it so that it can be checked.
Basically, I have yet to see a solution that can type check even the most trivial functions at runtime. When I say runtime typechecking I am including a requirement that JavaScript code be able to consume it unfettered. It should just happen.
That would mean the type system would be fully manifest in the value space and would not even be a language feature so much as a framework.
That could be a powerful framework and I might well use it. But I haven't seen a solution that works.
Taking that a bit further, if you have to write in TypeScript to call into it then it is not TypeScript anymore because it is not JavaScript anymore.
That would mean that all usages would need to encode sufficient information to type check all of their consumers.
I started on the side of adding this to the core of TypeScript, but I've come around to thinking it's best left to third-party libraries. I'd rather the TypeScript team remain completely focused on adding awesome features to the fully-erased static type system without having to consider, each time they add a feature, its implication on some runtime validation system. TypeScript's excellent type system enables perfectly usable library solutions to this problem:
https://github.com/gcanti/io-ts
https://github.com/pelotom/runtypes
IMO, that's pretty ideal; if a feature can be implemented as a library instead of as a core part of the language, it should be.
@pelotom Makes total sense athough would be a lot easier for the TypeScript team to maintain than others.
Does such a library exist currently? The two examples you included don't appear to modify compiler output to include run time exceptions on unexpected types.
@pelotom That's kinda what I've thought. Given that TypeScript's story is very similar Typed Racket, it makes sense to implement a function contract decorator library. I wish I had time to port clojure.spec to TypeScript.
@nbransby I'm not sure if such a library exists, but why do you need it to be a library that modifies compiler output?
https://github.com/Microsoft/TypeScript/issues/13835
as mentioned by others ideally for me this would be a compiler flag to create non-production ready code to help debug issues. A bit like turning optimisations off on a traditional compiler.
Related trivia: Googles j2objc which trans-compiles java to objective c puts in these runtime checks to ensure generated code behaves like java - for example Objective C doesn't blow up on dereferencing nil so every occurrence of this in the generated code is wrapped in a function call: nil_chk, which throws a null pointer exception if nil
Compiler output is what we're talking about here. TypeScript already forces
us (for example) to check for null otherwise it throws a compile-time
error. So, if we are forced to write null checks, and output code is
therefore going to have null checks (for example), then why not just
automate it? Etc.
On Apr 3, 2017 2:21 PM, "nbransby" notifications@github.com wrote:
13835 https://github.com/Microsoft/TypeScript/issues/13835
as mentioned by others ideally for me this would be a compiler flag to
create non-production ready code to help debug issues. A bit like turning
optimisations off on a traditional compiler.Related trivia: Googles j2objc which trans-compiles java to objective c
puts in these runtime checks to ensure generated code behaves like java -
for example Objective C doesn't blow up on dereferencing nil so every
occurrence of this in the generated code is wrapped in a function call:
nil_chk, which throws a null pointer exception if nil—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1573#issuecomment-291277778,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AASKziOHTip5Z7JctdXWm7_UrnEBGOYQks5rsWLzgaJpZM4DNGGs
.
I'm actually currently implementing something like that, as it doesn't seem such a feature will be added to the compiler soon.
My aim was to utilize the new transformer API (scheduled for TypeScript 2.3 and already in the nightly builds) with TypeScript as a dependency. I think that this has several advantages over creating a TypeScript fork, which has to be maintained separately.
I'm trying to adapt as many features from the specifications as possible (e.g. Interface Declaration Merging) and I hope there won't be many limitations.
For the runtime code I'm making use of flow-runtime, which offers almost any type reflection mechanisms needed.
I will publish a first draft version of the code soon and I'm looking forward to your feedback.
@fabiandev awesome! Did you also take a look at https://github.com/gcanti/io-ts ?
Someone already mentioned tcomb : something interesting is there's a babel plugin to turn flow annotations in tcomb validation : https://github.com/gcanti/babel-plugin-tcomb
It should not be too difficult to adapt it for Typescript.
@jedmao I did have a look at io-ts, but decided to go with flow-runtime though, mostly because of its documentation and live demo. Anyway, the runtime representation should not be too difficult to change. I may add a way to make it entirely replaceable in the future.
@abenhamdine babel-plugin-flow-runtime does that job already pretty well. I don't think creating a babel plugin is that easy right now. As far as I know, babel doesn't understand TypeScript, but Flow according to https://github.com/Microsoft/TypeScript/issues/11441#issuecomment-252336980. Also see https://github.com/babel/babylon/issues/320 and https://github.com/babel/babel/issues/5201. I also thought I'll give the transformer API a try.
@fabiandev thx for the explanation !
Hang in there, can't wait to try your work 😄
@pelotom I took a look at https://github.com/pelotom/runtypes and I have to say I really like the direction you've taken.
The explicit method contracts approach and the tidy dsl you've built up around it seem quite elegant.
@aluanhaddad thanks!
https://github.com/gcanti/babel-plugin-tcomb is very useful for development when migrating from plain JS, because it gives real confidence if you type core parts. Given the amount of bugs that both Flow and TypeScript have at the moment such asserts give real confidence and I would love if TypeScript had flag to turn on runtime type checking based on annotations. They are also really helpful for checking API boundaries with exact object checking.
I've just published my work on generating runtime type checks with the TypeScript transformation API: https://github.com/fabiandev/ts-runtime
There are some limitations at this point and there's still a lot of room for improvements, but I'd love to hear what you think. Feel free to open issues and/or to send PRs.
Look great, thanks @fabiandev
I've created a playground for ts-runtime, similar to TypeScript's playground, to try it in a browser: https://fabiandev.github.io/ts-runtime/
@fabiandev I tried this snippet and the results were quite impressive
interface A<T> {
prop: T;
}
function f(x: A<number>, y: A<number>): A<number> {
x.prop = x.prop - y.prop;
return x;
}
const x: A<number> = {
prop: 1
};
const g: (x: any, y: any) => any = f;
// fails with "Uncaught RuntimeTypeError: x.prop must be a number"
const y = g({prop: 'a'}, {prop: 'b'});
alert(y.prop);
I would say that this is _not_ TypeScript anymore but, whatever it is, it sports impressively early and informative errors.
@aluanhaddad thank you, glad you like it! I'm currently making use of flow-runtime for runtime type reflections and assertions, while for modifying the AST and emitting the resulting code, the TypeScript compiler API is used. There are still quite some things that can be improved and some limitations apply, due to both, ts-runtime and flow-runtime, but I hope it will evolve over time.
@fabiandev -
Looking very good!
How far away is this from full support of TypeScript type system?
I'd imagine a lot of people will want to use this.
Thanks @Marak! There are still a few conditions that have yet to be implemented. Also I'm considering switching to io-ts from flow-runtime, as some browser benchmark tests I've performed lately were not really satisfactory, and its behavior is not as expected in some cases.
How is this going?... having a flag for debugging purposes is definitely a plus, I can still imagine situations where you can run into problems, eg in a react reducer:
interface ActionType {
type: string,
payload?: any
}
export default function myReducer(state: MyStateType, action: ActionType): MyStateType{
if (action.type === "MY_ACTION"){
let data: MyStateType = action.payload;
return data;
}
return state;
}
I don't understand the "bad performance" argument. If you only enforce type checking for variables that are declared as "runtime checkable", say RunType<Foo>
, then by default the types will remain unchecked until you wrap them in that special generic type, so no added overhead.
It is wasteful and useless to do runtime checks on every assignment since the compiler can rule out many cases of illegal assignments statically.
However, runtime type checking can be useful at specific moments during run time, specifically when receiving data from a source that one knows is not check-able statically.
Therefore, it would be useful to have some compiler support for generating a runtime structural validation function that can be called at will on objects of type any
that returns objects of the desired type (essentially like a static type guard) or throw an exception.
For example:
interface Employee {
name: string;
salary: number;
}
function webServiceHandler ( possibleEmployee: any ) {
try {
const actualEmployee = <runtime Employee>possibleEmployee;
// actualEmployee is of type Employee here.
}
catch (e) {
// validation failed;
}
}
I don't propose this particular syntax at all (though I find it a particularly clean and readable pattern that's easy to transpile to a function call). Perhaps this could be implemented with annotations or some other mechanism but I absolutely believe that having the compiler generate a proper runtime type checker automatically would be very very useful.
@saabi I think it would be nicer to simply declare any type you need runtime-checked to be a generic like RunType<Foo>
- that way you don't need to worry about explicitly checking the type. The compiler could figure out when it needs to insert that check and do it automatically and you write the code as normal.
@jez9999 It is difficult if not impossible for the compiler to decide where a runtime check would be needed. Since it can't know in advance who will be calling an exported method, the naive alternative is to check types at every public call site where the type is used which is wasteful.
It must be specified by the programmer where he deems it necessary.
The pattern I described also allows precise control of what to do on the event of a validation failure.
@saabi I'd imagine it would be whenever the variable was modified, like assignment and delete
? When else might it be needed?
@jez9999 That's uses up even more CPU resources than checking at every parameter assignment on every call site. It would make it useless to me and many other programmers.
Typically there's only a few places, which only a developer can determine that need runtime type checking. Such as when receiving data from an untrusted source like a web service client.
Additionally, in the event of a validation failure, checking everywhere without clearly marked try {} catch {}
blocks makes the code more difficult to understand and control.
Perhaps annotations could be used for marking arguments coming from untrusted sources, but I still prefer a syntax similar to mine, where I can control precisely when the type checking occurs.
Requiring manual specification of when/where/how runtime checks are executed seems reasonable to me (and even if that entails the use of explicit runtime library invocation as opposed to native support in type decls). It seems to me the important thing is to be able to reuse existing type definitions as much as possible without having to duplicate them in a different syntax/tool.
Yeah, I come from the C# world where we have the luxury of being able to assume that a class is unchangeable. It'd be nice to have that for select variables in TS.
As a Flow user and casual observer of TypeScript, I have to say, I love the concept of flow-runtime
. After seeing numerous ugly, hard-to-read runtime type checking APIs over the years, flow-runtime
was a huge relief, because there's really no better syntax for expressing type assertions than the flow types themselves, and the same would go for TypeScript:
const json = {
name: 'Andy',
email: '[email protected]',
};
type UserInfo = {
name: string,
email: string,
phone?: string,
};
(json: UserInfo); // performs a runtime type check
However, flow-runtime
still has a few rough edges, and the primary limitation of babel-plugin-flow-runtime
is that babel
plugins can only process one file at a time, so it has no good way to determine if types being imported from another module were actually converted to runtime types. I assume the TypeScript compiler is significantly more well-prepared in this aspect.
@saabi if you want to support manual type checking only, it can't use the compile-time type cast syntax, because there are times you have to cast something solely to satisfy the compiler, right?
With flow-runtime
, if you disable automatic assertions, you can still perform them manually like this:
import {reify} from 'flow-runtime'
import type {Type} from 'flow-runtime'
type User = {
name: string,
email: string,
}
const UserType = (reify: Type<User>)
UserType.assert({
name: 'Andy',
email: '[email protected]',
})
@jedwards1211
My suggestion is not to use the syntax as is, but to add a runtime
keyword such as <runtime SomeType>someReference
for manual runtime checks, thus clearly differentiating a compile time check from a runtime check.
Oh I see, cool!
@RyanCavanaugh Please revisit this, and especially consider doing it in a non-production environment, or always doing it when encountering a runtime
keyword (see @saabi's comment).
Perhaps the design goals could be expanded to include this?
From the goals
Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
This is ironic, given than TypeORM relies heavily on runtime metadata about decorators AFAIK.
The problem with this goal is there's just no better way to define types for validating things like JSON documents than with the syntax of TypeScript/Flow itself. Until runtime type introspection becomes common, we'll be stuck with an excess of crappy validation libraries that are nowhere near as elegant.
This seems like a perfect use case for typescript macros 👍
So if I'm building a library that can be adopted by plain javascript users, it's prone to weird errors for them. It'd be cool to have a flag for JS builds to do complete type checking.
So if users are using TS, they don't get these type checks. If they are using the JS build, they get them.
Not any more prone than any regular Javascript library. Actually, Javascript users can trust code generated by the Typescript compiler a lot more.
The real usefulness lies in localized dynamic type checks for the reverse situation.
When Typescript code must use data coming from an untrusted (more specifically non type checked) environment (that means from Javascript code, or from network transmitted JSON) it could definitely use a statically auto-generated dynamic type check at the receiving location.
@jedwards1211
This is ironic, given than TypeORM relies heavily on runtime metadata about decorators AFAIK.
The problem with this goal is there's just no better way to define types for validating things like JSON documents than with the syntax of TypeScript/Flow itself. Until runtime type introspection becomes common, we'll be stuck with an excess of crappy validation libraries that are nowhere near as elegant.
Perhaps I am beating a dead horse, but decorator metadata does not represent types. It embeds values based on compile time types that correspond to conveniently named (if conflation is _desired_) runtime values.
https://github.com/fabiandev/ts-runtime uses an orthogonal approach and with good reason.
@aluanhaddad I don't see a good reason. C# is strict typed at runtime.
I build a very simple runtime validation library for this purpose as well that I use for validating JSON api requests.
https://github.com/ccorcos/ts-validator
At the heart of it is this simple validator type:
export type Validator<T> = (value: T) => boolean
But then you can create compositions of this type:
type Purify<T extends string> = { [P in T]: T }[T]
export type ObjectSchema<T extends object> = {
[key in Purify<keyof T>]: Validator<T[key]>
}
And at the end of the day, you can a create type-safe runtime validation functions at are 1:1 with the type interface:
import * as validate from "typescript-validator"
interface User {
id: number,
name?: string,
email: string,
workspaces: Array<string>
}
const validator = validate.object<User>({
id: validate.number(),
name: validate.optional(validate.string()),
email: validate.string(),
workspaces: validate.array(validate.string())
})
const valid = validator({id: 1, email: "hello", workspaces: []})
Would be cool if these functions could be generates in typescript, but I suppose its not that necessary.
@saabi Actually the syntax can be much more easier by using as
operator.
interface Employee {
name: string;
salary: number;
}
function webServiceHandler ( possibleEmployee: any ) {
try {
const actualEmployee = possibleEmployee as Employee;
// Code for type assertion should be generated when the compiler
// spot that we are casting `any` to a specific type
}
catch (e) {
// validation failed;
}
}
@wongjiahau That would have to be optional, it's currently the best way to access properties on objects that don't have them in their type. Unfortunately it's quite common when interfacing with other javascript or global objects on web pages and this check would break them.
example
function setup(stack: any[]) {
if ((stack as any).setupDone) {
return;
}
// ...
(stack as any).setupDone = true;
}
Anyway, will anyone support me if I'm going to implement this feature by forking this project?
(I will _seriously_ implement this if I get more than 20 reactions)
Apologies, my example used a non-any type when you specified casting any -> non-any.
Still, it's a backwards-incompatible change which means you're fighting an uphill battle. That's why other people have suggested new syntax such as !
.
@wongjiahau I wouldn't necessarily want all "as" operators to create type checking code around it. I'd much rather be explicit with it with a simple !
on a case-by-case basis. Did you take a look at io-ts?
@jedmao I did look at io-ts
, I actually wanted to use it but it is not really natural when you have nested type.
@tbillington Ok now I understand your concern (of the backwards-compatibility).
So, I guess the syntax should looks like this as suggested by @jedmao?
function square1(x: number!) {
// TS compiler will generate code that will assert the type is correct
return x * x;
}
function square2(x: number) {
// TS compiler will not generate any type-assertion code
return x*x;
}
var apple: any = "123";
square1(apple); // Compiler will not throw error, since the type of `apple` is `any`.
// But this code will cause error to be thrown at runtime
square2(apple); // Compiler will throw error
var banana = "123";
square1(banana); // Compiler will generate error because you can't cast string to number by any means
So, the !
operator is actually suppressing error that is related to casting from any
to specific type.
@jedmao @tbillington What do you think?
@wongjiahau my opinion is that the compiler errors should be exactly as they are w/o change. The only difference is runtime. As such, I don't see how the !
operator would suppress any compiler errors at all.
Let's take your example:
function square1(x: number!) {
// TS compiler will generate code that will assert the type is correct
return x * x;
}
The TS compiler would simply emit something akin to the following:
function square(x) {
if (typeof x !== 'number') {
throw new TypeError('Expected a number.');
}
return x * x;
}
Super easy with primitive types, but more complicated with interfaces and more complex types (but io-ts solves that issue).
I just wish it were built into the compiler itself so we didn't have to do all this extra stuff. But it really is complex and requires a lot of thought behind it in order to do it right.
Here's some other ideas I thought of recently:
// First line of file
const x: number! = 42; // compiler error: redundant type checking on a known type.
const x: number = 42;
const y: number! = x; // also redundant
const x: number = 42;
const y: string! = x; // compiler error:
const z: string! = x as any; // OK, but should it be?
There's really a lot to think about here.
@jedmao I understand what you say, but please allow me to clarify the error suppression with a typical example.
Suppose you have the following code (using express.js
) :
interface Fruit {
name: string,
isTasty: boolean
}
app.post("/uploadFruit", (req, res) => {
const newFruit = req.body; // Note that `req.body` has type of `any`
// This line shouldn't throw compile error because we already tell the compiler
// to assert that newFruit is type of `Fruit` by adding the `!` operator
saveFruit(newFruit);
});
function saveFruit(newFruit: Fruit!) {
// save the fruit into database
}
If that line of saveFruit(newFruit)
will throw compilation error, then this runtime typechecking feature would be meaningless already.
I hope you can understand what I'm trying to say.
Moreover, I think that syntax A will be better than syntax B
// syntax A
const apple!: string = "...";
// syntax B
const apple: string! = "...";
This is because syntax A will be much more easier to be parsed, and it is also more consistent with the ?:
operator.
So you can read the !:
operator as must be a
.
@wongjiahau I'm not suggesting that saveFruit(newFruit)
should throw a compiler error, as req.body
is of type any
. Just like it won't throw a compiler error today, it shouldn't in the future. That's exactly what I said before. Current compiler errors should remain compiler errors, period.
That said, I still don't see where any error is being "suppressed" as you say.
I do kinda' like the !:
operator idea. That would make forced union types a lot easier to read:
function foo(x!: string | number) {}
@jedmao Sorry for the misunderstanding, because I thought the compiler will throw error if we cast from any
to a specific type, just realized it won't throw error.
So, do you guys want to fork this project and implement this feature?
I think Microsoft would have to agree to accept a community PR for me to invest time in this. Also, I just don't have the bandwidth.
@jedmao I'm actually thinking of forking it as another project (perhaps I'll call it Experimental-Typescript ? ).
Because I notice there are a lot of potential pull request and issues that will not be implemented in this project, I'm thinking of merging them and implementing them.
Since we're talking a lot about validation of unsafe data, I think Phantom types are relevant:
https://medium.com/@gcanti/phantom-types-with-flow-828aff73232b
Currently, its not possible in Typescript.
I see some people made libraries for runtime checking but I would show another one (created by myself):
https://github.com/ThaFog/Safetify
Well i's not stricte for runtime type checking because the major difference comparing to others above is that it doesn't throw errors but simply replacing incorrect values with type-safe equivalents. So it's better to use in webservices, with taking data from API or so.
However I'm planning to add features like optional errors throwing, global onError callbacks, constraints, json or function resolvers. then it would be more for runtime checking
Creator of Node.js seems to be working on similar project for TypeScript: https://github.com/ry/deno "A secure TypeScript runtime on V8"
Interesting. I checked out the repo -- still pretty unclear what it's for...
Most helpful comment
@RyanCavanaugh it's been almost 2 years since this feature request was made and I'm wondering what it would take for the design goals to change or make an exception for this particular request? Now that the initial TypeScript roadmap is complete, perhaps we can entertain this idea?
I think this proposed compiler flag would only make sense in development and staging environments, but it would serve as a great tool for debugging the application at runtime.
Consider the scenario in which an API request is made and we expect the API response to adhere to a model (interface).
Compiles to:
Rather than getting some error about some
foo
prop being undefined, wouldn't it be more useful to know that the complex type that was defined as input didn't come back the way we expected?