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

Allow equality comparisons to undefined and null in strict null checking mode #8452

Merged
merged 2 commits into from
May 4, 2016

Conversation

ahejlsberg
Copy link
Member

With this PR the ==, !=, ===, and !== operators can be used to compare any type to undefined or null in strict null checking mode. This makes it possible to practice "defensive coding" that checks for invalid arguments at run-time, for example:

function foo(obj: Object) {
    if (obj == null) {
        throw new TypeError("Argument cannot be undefined or null");
    }
    // Do something with obj
}

Even through TypeScript wouldn't permit undefined or null to be passed to the function, it may be useful to guard against invalid arguments passed from plain JavaScript code.

@ahejlsberg
Copy link
Member Author

Fixes #8439.

@RyanCavanaugh
Copy link
Member

👍

@ahejlsberg ahejlsberg merged commit cb9be66 into master May 4, 2016
@ahejlsberg ahejlsberg deleted the equalityUndefinedAndNull branch May 4, 2016 00:52
@myitcv
Copy link

myitcv commented May 4, 2016

@ahejlsberg would it be possible to automatically emit runtime checks at the "perimeter" of compiled TypeScript code? That is to say, exported functions/methods/etc with non-null parameters would have their arguments checked (in much the same way as you have manually coded above). Similarly return values from external function/method calls would also be checked, all seamlessly at runtime.

This would effectively give a true runtime guarantee of non-null/undefined.

Quite how you throw in this situation I'm not sure though... because you wouldn't want the exception to be caught by anything

@malibuzios
Copy link

malibuzios commented May 4, 2016

I have been thinking about this myself for a long time, but I avoided mentioning it as it seems like the team has shown strong aversion to run-time checks of any kind, however effective and desirable they are in practice for particular scenarios.

I would like to highlight though, that argument checks are different in the sense that they should not be considered an "emit based on type information". The compiler doesn't generally assume any information on what it gets, as the analysis of calls is not generally contextual. The annotated type could be simply used as a key for a deterministic lookup into a list of predetermined and user-defined functions. I believe that is a very important point that must be made clear.

If user-defined type guards were designed with reverse lookups in mind, it could have provided a tremendous benefit for libraries, especially those written particularly with plain JS consumers in mind. These checks could have been emitted conditionally based on a compiler switch or a pseudo-decorator annotation (or both).

I don't believe that alternative solutions like decorators and reflective metadata would provide the level of elegance, user friendliness and sophistication that a built-in one would. Using decorators is essentially like requiring every developer to 'reinvent the wheel' in order to solve something that is generally well-defined and could have been easily automated.

@myitcv
Copy link

myitcv commented May 5, 2016

@malibuzios apologies, I don't really understand what you're getting at.

To make concrete my suggestion, given the following exported function:

// TypeScript - strictNullCheck
export function foo(obj: Object) {
    // Do something with obj
}

something like the following would be emitted:

// emitted Javascript
define(["require", "exports"], function (require, exports) {
    "use strict";
    function foo(obj) {
        if (obj == null) {
            // NOTE the following error must _not_ be caught by any catch
            throw new TypeError("Argument cannot be undefined or null");
        }
        // Do something with obj
    }
    exports.foo = foo;
});

Similarly for the following call to an external function:

// TypeScript - strictNullCheck
import * as MyLib from "my-external-lib";

// As defined in someFile.d.ts
// MyLib.MyFunction(): string
let x = MyLib.MyFunction();
// Do something with x

something like the following would be emitted:

// emitted Javascript
define(["require", "exports", "my-external-lib"], function (require, exports, MyLib) {
    "use strict";
    // As defined in someFile.d.ts
    // MyLib.MyFunction(): string
    var x = MyLib.MyFunction();
    if(x == null) {
        // NOTE the following error must _not_ be caught by any catch
        throw new TypeError("Return val cannot be undefined or null");
    }
    // Do something with x 
});

@malibuzios
Copy link

malibuzios commented May 5, 2016

@myitcv

I was referring to a more thorough and generalized runtime-checking solution that would go way beyond only checking for null and undefined. It would use the annotated types to check that an argument annotated string is actually a string, would understand concepts like unions, when to use instanceof, etc. could generate predicates based on analyzing interface structures, etc.

It could be assisted by user-defined guards as well. However, they were not designed with that in mind, to such a degree that many of them are not even suitable for run-time checks at all (including in decorators) as they encourage the programmer to apply unsound assumptions (there are many real-world examples of that people give here). Essentially promoting an 'indirect' emit on type information, although this may seem subtle.

I'm sorry if I'm using overly technical concepts like 'emit on type information' and not giving enough examples. I think this may be content for a different issue. However, I don't feel much motivation to try to write a proposal as it seems very likely it would be (harshly, or even rudely) rejected.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants