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

Runtime type checking #1573

Closed
jedmao opened this Issue Dec 30, 2014 · 93 comments

Comments

Projects
None yet
@jedmao
Contributor

jedmao commented Dec 30, 2014

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.

@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Dec 30, 2014

Contributor

Perhaps something more akin to google AtScript for consistency. Basically flag --rtts will cause emited JS to have rtts (runtime type system) checks inserted.

Contributor

basarat commented Dec 30, 2014

Perhaps something more akin to google AtScript for consistency. Basically flag --rtts will cause emited JS to have rtts (runtime type system) checks inserted.

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Dec 30, 2014

Contributor

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.

Contributor

jedmao commented Dec 30, 2014

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.

@RyanCavanaugh

This comment has been minimized.

Show comment
Hide comment
@RyanCavanaugh
Member

RyanCavanaugh commented Dec 30, 2014

This remains outside our design goals (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)

@davidrapin

This comment has been minimized.

Show comment
Hide comment
@davidrapin

davidrapin May 11, 2016

For people like me coming here looking with high hopes, you might want to take a look at http://angular.github.io/assert/

davidrapin commented May 11, 2016

For people like me coming here looking with high hopes, you might want to take a look at http://angular.github.io/assert/

@jeansson

This comment has been minimized.

Show comment
Hide comment
@jeansson

jeansson Dec 7, 2016

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?

jeansson commented Dec 7, 2016

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?

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Dec 7, 2016

Contributor

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.

Contributor

aluanhaddad commented Dec 7, 2016

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.

@jeansson

This comment has been minimized.

Show comment
Hide comment
@jeansson

jeansson Dec 7, 2016

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..

jeansson commented Dec 7, 2016

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..

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Dec 7, 2016

Contributor

But, IMO, you need to learn to love JavaScript to fully take advantage of TypeScript.

Contributor

aluanhaddad commented Dec 7, 2016

But, IMO, you need to learn to love JavaScript to fully take advantage of TypeScript.

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Dec 7, 2016

Contributor

@jeansson did you take a look at tcomb?

Contributor

jedmao commented Dec 7, 2016

@jeansson did you take a look at tcomb?

@jeansson

This comment has been minimized.

Show comment
Hide comment
@jeansson

jeansson Dec 7, 2016

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)

jeansson commented Dec 7, 2016

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)

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Dec 7, 2016

Contributor

@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?

Contributor

jedmao commented Dec 7, 2016

@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?

@jeansson

This comment has been minimized.

Show comment
Hide comment
@jeansson

jeansson Dec 8, 2016

@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

jeansson commented Dec 8, 2016

@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

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Dec 8, 2016

Contributor

@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.

Contributor

aluanhaddad commented Dec 8, 2016

@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.

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Dec 8, 2016

Contributor

@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.

Contributor

jedmao commented Dec 8, 2016

@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.

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Dec 9, 2016

Contributor

I am not recommending it as a solution.

Contributor

aluanhaddad commented Dec 9, 2016

I am not recommending it as a solution.

@jeansson

This comment has been minimized.

Show comment
Hide comment
@jeansson

jeansson Dec 9, 2016

@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?

jeansson commented Dec 9, 2016

@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?

@GulinSS

This comment has been minimized.

Show comment
Hide comment
@GulinSS

GulinSS Dec 21, 2016

Look https://github.com/codemix/babel-plugin-typecheck. It could work like this but automatically.

Edit: found same tip: #7607 (comment)

GulinSS commented Dec 21, 2016

Look https://github.com/codemix/babel-plugin-typecheck. It could work like this but automatically.

Edit: found same tip: #7607 (comment)

@nevir

This comment has been minimized.

Show comment
Hide comment
@nevir

nevir Jan 2, 2017

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

nevir commented Jan 2, 2017

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

@JRGranell

This comment has been minimized.

Show comment
Hide comment
@JRGranell

JRGranell Jan 5, 2017

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.

https://codemix.github.io/flow-runtime/

JRGranell commented Jan 5, 2017

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.

https://codemix.github.io/flow-runtime/

@danielo515

This comment has been minimized.

Show comment
Hide comment
@danielo515

danielo515 Jan 27, 2017

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.

danielo515 commented Jan 27, 2017

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.

@Marak

This comment has been minimized.

Show comment
Hide comment
@Marak

Marak Jan 27, 2017

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.

Marak commented Jan 27, 2017

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.

@jedwards1211

This comment has been minimized.

Show comment
Hide comment
@jedwards1211

jedwards1211 Dec 7, 2017

Oh I see, cool!

jedwards1211 commented Dec 7, 2017

Oh I see, cool!

@aaronshaf

This comment has been minimized.

Show comment
Hide comment
@aaronshaf

aaronshaf Dec 19, 2017

@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?

aaronshaf commented Dec 19, 2017

@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?

@jedwards1211

This comment has been minimized.

Show comment
Hide comment
@jedwards1211

jedwards1211 Dec 19, 2017

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.

jedwards1211 commented Dec 19, 2017

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.

@ccorcos

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Jan 16, 2018

This seems like a perfect use case for typescript macros 👍

ccorcos commented Jan 16, 2018

This seems like a perfect use case for typescript macros 👍

@j

This comment has been minimized.

Show comment
Hide comment
@j

j Feb 13, 2018

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.

j commented Feb 13, 2018

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.

@saabi

This comment has been minimized.

Show comment
Hide comment
@saabi

saabi Feb 13, 2018

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.

saabi commented Feb 13, 2018

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.

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Feb 15, 2018

Contributor

@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.

Contributor

aluanhaddad commented Feb 15, 2018

@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.

@jez9999

This comment has been minimized.

Show comment
Hide comment
@jez9999

jez9999 Apr 3, 2018

@aluanhaddad I don't see a good reason. C# is strict typed at runtime.

jez9999 commented Apr 3, 2018

@aluanhaddad I don't see a good reason. C# is strict typed at runtime.

@ccorcos

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Apr 3, 2018

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.

ccorcos commented Apr 3, 2018

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.

@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

@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 commented Apr 24, 2018

@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;
    }
}
@tbillington

This comment has been minimized.

Show comment
Hide comment
@tbillington

tbillington Apr 24, 2018

@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;
}

tbillington commented Apr 24, 2018

@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;
}
@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

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)

wongjiahau commented Apr 24, 2018

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)

@tbillington

This comment has been minimized.

Show comment
Hide comment
@tbillington

tbillington Apr 24, 2018

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 !.

tbillington commented Apr 24, 2018

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 !.

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Apr 24, 2018

Contributor

@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?

Contributor

jedmao commented Apr 24, 2018

@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?

@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

@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 commented Apr 24, 2018

@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?

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Apr 24, 2018

Contributor

@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.

Contributor

jedmao commented Apr 24, 2018

@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.

@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

@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 commented Apr 24, 2018

@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.

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Apr 24, 2018

Contributor

@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) {}
Contributor

jedmao commented Apr 24, 2018

@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) {}
@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

@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?

wongjiahau commented Apr 24, 2018

@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?

@jedmao

This comment has been minimized.

Show comment
Hide comment
@jedmao

jedmao Apr 24, 2018

Contributor

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.

Contributor

jedmao commented Apr 24, 2018

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.

@wongjiahau

This comment has been minimized.

Show comment
Hide comment
@wongjiahau

wongjiahau Apr 24, 2018

@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.

wongjiahau commented Apr 24, 2018

@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.

@ccorcos

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Apr 24, 2018

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.

ccorcos commented Apr 24, 2018

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.

@ThaFog

This comment has been minimized.

Show comment
Hide comment
@ThaFog

ThaFog Apr 24, 2018

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

ThaFog commented Apr 24, 2018

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

@Ciantic

This comment has been minimized.

Show comment
Hide comment
@Ciantic

Ciantic May 30, 2018

Creator of Node.js seems to be working on similar project for TypeScript: https://github.com/ry/deno "A secure TypeScript runtime on V8"

Ciantic commented May 30, 2018

Creator of Node.js seems to be working on similar project for TypeScript: https://github.com/ry/deno "A secure TypeScript runtime on V8"

@ccorcos

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos May 31, 2018

Interesting. I checked out the repo -- still pretty unclear what it's for...

ccorcos commented May 31, 2018

Interesting. I checked out the repo -- still pretty unclear what it's for...

@mhegazy mhegazy referenced this issue Jul 11, 2018

Closed

Pass Template Arguments to Function #25585

3 of 4 tasks complete

@Microsoft Microsoft locked and limited conversation to collaborators Jul 31, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.