Non-nullable types #7140

Merged
merged 65 commits into from Mar 21, 2016

Projects

None yet
@ahejlsberg
Member
ahejlsberg commented Feb 18, 2016 edited

This PR implements support for null- and undefined-aware types and strict null checking that can be enabled with a new --strictNullChecks compiler switch. For context on this topic, see discussion in #185.

Null- and undefined-aware types

TypeScript has two special types, Null and Undefined, that have the values null and undefined respectively. Previously it was not possible to explicitly name these types, but null and undefined may now be used as type names regardless of type checking mode.

The type checker previously considered null and undefined assignable to anything. Effectively, null and undefined were valid values of every type and it wasn't possible to specifically exclude them (and therefore not possible to detect erroneous use of them). This changes in the new strict null checking mode.

In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any (the one exception being that undefined is also assignable to void). So, whereas T and T | undefined are considered synonymous in regular type checking mode (because undefined is considered a subtype of any T), they are different types in strict type checking mode, and only T | undefined permits undefined values. The same is true for the relationship of T to T | null.

// Compiled with --strictNullChecks
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1;  // Ok
y = 1;  // Ok
z = 1;  // Ok
x = undefined;  // Error
y = undefined;  // Ok
z = undefined;  // Ok
x = null;  // Error
y = null;  // Error
z = null;  // Ok
x = y;  // Error
x = z;  // Error
y = x;  // Ok
y = z;  // Error
z = x;  // Ok
z = y;  // Ok

Assigned-before-use checking

In strict null checking mode the compiler requires every reference to a local variable of a type that doesn't include undefined to be preceded by an assignment to that variable in every possible preceding code path.

For example:

// Compiled with --strictNullChecks
let x: number;
let y: number | null;
let z: number | undefined;
x;  // Error, reference not preceded by assignment
y;  // Error, reference not preceded by assignment
z;  // Ok
x = 1;
y = null;
x;  // Ok
y;  // Ok

The compiler checks that variables are definitely assigned by performing control flow based type analysis. For further details on this topic, see #8010.

Optional parameters and properties

Optional parameters and properties automatically have undefined added to their types, even when their type annotations don't specifically include undefined. For example, the following two types are identical:

// Compiled with --strictNullChecks
type T1 = (x?: number) => string;              // x has type number | undefined
type T2 = (x?: number | undefined) => string;  // x has type number | undefined

Non-null and non-undefined type guards

A property access or a function call produces a compile-time error if the object or function is of a type that includes null or undefined. However, type guards are extended to support non-null and non-undefined checks. For example:

// Compiled with --strictNullChecks
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
    f(x);  // Ok, type of x is number here
}
else {
    f(x);  // Error, type of x is number? here
}
let a = x != null ? f(x) : "";  // Type of a is string
let b = x && f(x);  // Type of b is string?

Non-null and non-undefined type guards may use the ==, !=, ===, or !== operator to compare to null or undefined, as in x != null or x === undefined. The effects on subject variable types accurately reflect JavaScript semantics (e.g. double-equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).

Dotted names in type guards

Type guards previously only supported checking local variables and parameters. Type guards now support checking "dotted names" consisting of a variable or parameter name followed one or more property accesses. For example:

interface Options {
    location?: {
        x?: number;
        y?: number;
    };
}

function foo(options?: Options) {
    if (options && options.location && options.location.x) {
        const x = options.location.x;  // Type of x is number
    }
}

Type guards for dotted names also work with user defined type guard functions and the typeof and instanceof operators and do not depend on the --strictNullChecks compiler option.

A type guard for a dotted name has no effect following an assignment to any part of the dotted name. For example, a type guard for x.y.z will have no effect following an assignment to x, x.y, or x.y.z.

Expression operators

Expression operators permit operand types to include null and/or undefined but always produce values of non-null and non-undefined types.

// Compiled with --strictNullChecks
function sum(a: number | null, b: number | null) {
    return a + b;  // Produces value of type number
}

The && operator adds null and/or undefined to the type of the right operand depending on which are present in the type of the left operand, and the || operator removes both null and undefined from the type of the left operand in the resulting union type.

// Compiled with --strictNullChecks
interface Entity {
    name: string;
}
let x: Entity | null;
let s = x && x.name;  // s is of type string | null
let y = x || { name: "test" };  // y is of type Entity

Type widening

The null and undefined types are not widened to any in strict null checking mode.

let z = null;  // Type of z is null

In regular type checking mode the inferred type of z is any because of widening, but in strict null checking mode the inferred type of z is null (and therefore, absent a type annotation, null is the only possible value for z).

Non-null assertion operator

A new ! postfix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded. Similar to type assertions of the forms <T>x and x as T, the ! non-null assertion operator is simply removed in the emitted JavaScript code.

// Compiled with --strictNullChecks
function validateEntity(e: Entity?) {
    // Throw exception if e is null or invalid entity
}

function processEntity(e: Entity?) {
    validateEntity(e);
    let s = e!.name;  // Assert that e is non-null and access name
}

Compatibility

The new features are designed such that they can be used in both strict null checking mode and regular type checking mode. In particular, the null and undefined types are automatically erased from union types in regular type checking mode (because they are subtypes of all other types), and the ! non-null assertion expression operator is permitted but has no effect in regular type checking mode. Thus, declaration files that are updated to use null- and undefined-aware types can still be used in regular type checking mode for backwards compatiblity.

In practical terms, strict null checking mode requires that all files in a compilation are null- and undefined-aware. We are contemplating a pragma of some form that can be added to declaration files such that the compiler can verify that all referenced declaration files are null- and undefined-aware in a compilation that uses strict null checking.

@ahejlsberg ahejlsberg Merge branch 'master' into strictNullChecks
Conflicts:
	src/compiler/checker.ts
	src/compiler/types.ts
44d7897
@RyanCavanaugh
Member

This compiles without error under --strictnullchecks

let x: string;
x.substr(10);

while the equivalent

let x: string = undefined;
x.substr(10);

does not

@ahejlsberg
Member

@RyanCavanaugh Yup, that's the definite assignment analysis work item I've got listed in the remaining work section.

@robertknight

Note: We use postfix notation for the ? operator to avoid ambiguities in types
like string?[] and string[]?. We may consider also supporting prefix notation similar to
the Flow type checker.

If Flow and TypeScript can agree on syntax for this and thereby enable code to be parsed with either TypeScript or Babel that would be very helpful for projects wishing to try one or the other.

ahejlsberg added some commits Feb 19, 2016
@ahejlsberg ahejlsberg Support x == null and x != null in non-null guards. Also, allow == an…
…d != in type guards.
50ea0bf
@ahejlsberg ahejlsberg Accepting new baselines
d10017f
@myitcv
myitcv commented Feb 19, 2016

👍 to this work

type guards are extended to support non-null checks.

Can I confirm the following is also a valid type guard on x?

// Compiled with --strictNullChecks
interface Entity {
    name: string;
}
let x: Entity?;

if (x != undefined) {
    // x is of type Entity
    // guaranteed runtime safe use of x
}

Expression operators permit the operands to be both nullable and non-nullable and always produce values of non-nullable types.

Please can you explain the rationale behind this?

I would have thought the following a more intuitive interpretation:

// Compiled with --strictNullChecks
function sum(a: number?, b: number?, c: number, d: number) {
    console.log(a + b);  // error
    console.log(a + c);  // error
    console.log(c + d);  // OK
}

A new ! postfix expression operator may be used to assert that its operand is non-nullable in contexts where the type checker is unable to conclude that fact.

Given the support you describe with type guards, can you explain why this is operator is necessary?

@tejacques

👍 very nice to see this!

There was some discussion in other issues about the if(x) style typeguards WRT falsy values.

For example:

let x: number? = 0;
let y: string? = '';
let z: boolean? = false;

In particular, boolean? guarding with if(z) feels pretty bad, since false is one of the two legitimate values. The other are unfortunate but not terrible.

It would be nice to have a guard work on x != null to get around this issue.

@ahejlsberg
Member

@myitcv Yes, x != undefined will work as a non-null guard (right now it only works with x != null but I will fix that).

The rationale for the expression operators is that they actually have well defined semantics for the null and undefined values. Certainly we want to support nullable values for the comparison operators, but I can see being more restrictive for the arithmetic operators. Still, not sure it merits the extra complexity.

The postfix ! assertion operator is useful in cases where the type checker can't conclude that a value is non-null but you know it to be true because of program logic. My description of the PR above gives an example of such a case.

@tejacques An x != null type guard already works.

@tejacques

Ah yes I see the commit now -- thanks @ahejlsberg! Very excited about this and I'll be testing it out extensively over the weekend.

@aleksey-bykov

Is this feature just a sugared view on a | void that has been used meanwhile?

ahejlsberg added some commits Feb 20, 2016
@ahejlsberg ahejlsberg Suport both x != null and x != undefined in non-null type guards
ed40fbf
@ahejlsberg ahejlsberg Merge branch 'master' into strictNullChecks
74d8c40
@JsonFreeman
Contributor

Somewhat related to @myitcv's question, do ProperyAccess and ElementAccess expressions permit their left operand types to be nullable? I couldn't find an error for this in the review, but figured it is likely disallowed by virtue of the union type representation.

Also, I just want to say this looks awesome!

@ahejlsberg
Member

@JsonFreeman (Hi, Jason!) Property and element accesses are disallowed when the left operand is nullable (as you say, because of the union type representation).

@JsonFreeman
Contributor

Thanks Anders! I'm happy to see this feature come together so well - I think it's a big step for the language :)

@ivogabe
Contributor
ivogabe commented Feb 20, 2016
  • Definite assignment analysis for non-nullable variables and properties in classes.

@ahejlsberg I think #6959 can be used for this (if it will be merged), as my PR can already narrow types based on assignments:

let x: string | number;
x = 42;
// x: number

That can also be used to narrow T? to T. In general, I think that those two features would be a nice addition to each other.

@ahejlsberg ahejlsberg Merge branch 'master' into strictNullChecks
5e5381d
@jeffreymorlan
Contributor

As described I don't think this will work very well with default parameters. I've found it's quite common to pass undefined as an argument to a function with default parameter values, either explicitly as undefined, or (more common) as the possibly-undefined value of an optional parameter. This proposal seems to break that:

function makeRange(min = 'A', max = 'Z') { /* ... */ }

makeRange(undefined, 'M'); // Safe - use default first param while specifying second
// Error: "Argument of type 'undefined' is not assignable to parameter of type 'string'."

makeRange(null, 'M'); // Bad, should still be an error

function makeBRange(max?: string) {
    return makeRange('B', max); // Safe - if max not specified, use default
    // Error: "Argument of type 'string?' is not assignable to parameter of type 'string'."
    // This pattern is very common in constructor super() calls
}

I would suggest that:

  • a default parameter of type T should have a signature type of T|undefined
  • an optional parameter declared of type T should have a type of T|undefined rather than T|null|undefined. (To allow it to be null as well, add the second question mark)
@ahejlsberg
Member

@jeffreymorlan I see what you're saying, but it would require treating null and undefined as distinct and incompatible types and users would have to reason about the subtle distinctions between T, T | undefined, T | null, and T? (which would be the same as T | null | undefined). Currently there's just T and T? which is a lot simpler. Not an easy choice to make.

@JsonFreeman
Contributor

I am actually surprised that a parameter with a default initializer is not nullable. Shouldn't it be treated just like a question token?

@ahejlsberg
Member

@JsonFreeman We currently infer a nullable type for a parameter only when the default value is nullable. This means you can't explicitly pass undefined (which is what @jeffreymorlan doesn't like), but it also means that within the function you don't have to use null guards. An alternate approach would be to type the parameter as T | undefined in the signature, but just T within the function. This would allow you to explicitly pass undefined but wouldn't force you to use type guards within the function.

@JsonFreeman
Contributor

Thanks @ahejlsberg. I do think it might make sense in this case to distinguish between the signature's parameter and the value declaration in the function's scope. The fact is that the former is allowed to be undefined, but the latter won't be. I think the same could be said of destructuring elements with default values, where the RHS value can be nullable, but the binding name will introduce a value declaration that's not nullable.

@samwgoldman

An alternate approach would be to type the parameter as T | undefined in the signature, but just T within the function.

For what it's worth, this is how it works in Flow.

@basarat
Contributor
basarat commented Feb 24, 2016

I have a few question regarding nullable members. Would like if allowed vs. not allowed can be answered below 🌹

interface Foo1 {
    foo?: number;
}
let foo1:Foo1;

interface Foo2 {
    foo: number?;
}
let foo2:Foo2;


foo1 = foo2; // allowed?
foo2 = foo1; // allowed?

foo1 = {}; // This is already allowed
foo2 = {}; // allowed?
@RyanCavanaugh
Member

@basarat running with the current branch I get (lines inlined):

foo2 = foo1; // allowed?
../../throwaway/clipboard.ts(13,1): error TS2322: Type 'Foo1' is not assignable to type 'Foo2'.
  Property 'foo' is optional in type 'Foo1' but required in type 'Foo2'.

foo2 = {}; // allowed?
../../throwaway/clipboard.ts(16,1): error TS2322: Type '{}' is not assignable to type 'Foo2'.
  Property 'foo' is missing in type '{}'.
@samwgoldman

How does subtyping work w.r.t. nullables? Are object types property-wise invariant to account for aliasing hazards?

interface A {
  foo: string;
}
interface B {
  foo: string?;
}

let a: A = { foo: "foo" };
let b: B = a; // is this allowed?
b.foo = null; // because this would be bad, right?
@Arnavion
Contributor

Are object types property-wise invariant to account for aliasing hazards?

In general TS treats all types as covariant, which is also why it allows you to assign an Array<Cat> to an Array<Animal> and then assign a Dog to animals[0]

ahejlsberg added some commits Feb 27, 2016
@ahejlsberg ahejlsberg Support dotted names ("x.y.z") in type guards 3d7631d
@ahejlsberg ahejlsberg Fix getTypeOfSymbolAtLocation to handle hypothetical lookups 82169ce
@ahejlsberg ahejlsberg Accepting new baselines
7dd59ce
@ahejlsberg ahejlsberg Fix linting error
ea35932
@ahejlsberg
Member

Latest commits add support for "dotted names" in type guards. The description of the PR has been updated with a new section to describe this feature.

@Igorbek
Igorbek commented Feb 28, 2016

@ahejlsberg that's awesome!
What's about type inference for generics? Some thoughts come to my mind:

interface A { a: number; }
declare function foo<T extends A>(p: T?): void;
declare function boo<T extends A?>(p: T?): void;

let a: A;
let na: A?;

foo(na); // is 'T' = 'A' ?
boo(na); // is 'T' = 'A?' ? is 'p' of type 'A??' ? is it assignable from 'A?' ?
foo(a); // is 'T' = 'A' ?
boo(a); // is 'T' = 'A?' ? is 'p' of type 'A??' ? is it assignable from 'A' ?
foo<A?>(a); // is constraint not met?
@ahejlsberg
Member

@Igorbek Nullable types are just syntactic sugar for union types that include type of null/undefined. So, type inference works just like type inference for union types. In each of your first four examples the inferred type for T is A (because when inferring from either A to T? or A? to T?, we infer A for T). An error is reported in the last example because the explicitly specified type doesn't satisfy the constraint (A? isn't assignable to A).

Note that constraints have no effect on type inference, they aren't checked until after type inference is complete. Also, since a nullable type is just a union type, it simply isn't possible to have a type A?? (because for any two types X | Y is the same as X | Y | Y).

@basarat basarat referenced this pull request in basarat/typescript-book Feb 28, 2016
Open

Strict Null Checking : Nullable Types #80

@Igorbek
Igorbek commented Feb 28, 2016

Thank you, it makes sense 👍

@IanYates

Big 👍

In the explanation for this feature I see this

// Compiled with --strictNullChecks
type T1 = (x?: number) => string;   // x has type number?
type T2 = (x?: number?) => string;  // x has type number?

Is it fair to say

type T3 = (x: number?) => string;  // x has type number?

too? (that is, x is an optional parameter so if not specified it's undefined)

@DanielRosenwasser
Member

@IanYates I don't think that should be the case. If the author has listed something as nullable and not optional, they probably want users to explicitly specify the parameter (or they didn't know better).

@basarat
Contributor
basarat commented Feb 29, 2016

@IanYates agree with Daniel. x: number? doesn't make x optional, just makes it nullable. This is similar to the question I had #7140 (comment) a member doesn't become optional just becomes nullable 🌹

@IanYates

Thanks for the clarification. I should've actually looked at my own Typescript code before writing as I see what I actually wanted to ask was covered by T1 and I was confusing it with T3 (too much mixing of C# and Typescript on the weekend). But good to know anyway and what you've said makes makes good sense.

@ahejlsberg ahejlsberg Assigned-before-use checking for non-nullable variables
33e3825
@ahejlsberg
Member

Latest commit implements simple assigned-before-use checking that requires a reference to a local variable of a non-nullable type to be lexically preceded by at least one assignment to that variable. For example:

let x: number;
x;  // Error, reference not preceded by assignment
x = 1;
x;  // Ok

The analysis simply checks that an initializer or an assignment exists somewhere before each reference. It doesn't check that all possible code paths assign a value. For example, a missing assignment in one branch of an if statement is currently not flagged. We may implement more complete definite assignment analysis at some point, but it is beyond the scope of this PR. (Note that any solution we implement is unlikely to ever find all issues because it is in practice impossible to exhaustively reason about the effects of references to local variables from nested functions.)

@danquirk
Member
danquirk commented Mar 3, 2016

Awesome.

re: dotted names, does it work for union types like #1260 ?

@CyrusNajmabadi
Contributor

Very very cool. Is there any proposed facility to opt into --strictNullChecks on a more granular level. Putting it on at the entire compilation level would mean a lot of up front work to convert some existing code over to use this. If i could do this at a smaller level (say the file level), that would be super useful.

Thoughts?

@ahejlsberg
Member
ahejlsberg commented Mar 3, 2016 edited

@danquirk It doesn't include the property based type guards suggested in #1260, but we could discuss reviving that idea.

@CyrusNajmabadi Yes, I thought about a more granular level, but for reasons of simplicity I prefer a project wide switch. The core issue is that I don't see how it is possible to support file level opt-in without introducing a whole new kind of type. In a granular world it might be easy to change what syntax means in a particular file (i.e. string means "string with old semantics" or "string with new semantics" depending on file context), but the type checker would need to track the distinction in the types themselves as there's no notion of "the current file" when checking types. Unfortunately, the semantics (assignability, allowed operations, etc.) of a particular type T under the old rules match neither T nor T? under the --strictNullChecks rules, so we'd have to introduce a new internal representation (say T~) that means "T with old semantics" and then propagate that everywhere in the type system. This creates quite a lot of complexity and is not at all intuitive to reason about. My conclusion is that we're better off not going there.

@tinganho
Contributor
tinganho commented Mar 5, 2016

I'm just wondering if index expressions is always considered as nullable?

const s: string[];
function at(index: number): string {
    return s[index]; // error nullable is not assignable to non-nullable
}

Because an arbitrary index value can yield a void type.

@ahejlsberg
Member

@tinganho Indexing simply produces a value of the type declared in the matching index signature. Even if it is technically more correct, it would simply be too painful if we automatically added undefined to the type of every indexing operation. For example, every array element access would have to be accompanied by a non-null guard or a ! assertion. I think that would become hugely irritating.

@JsonFreeman
Contributor

It's hard to say. I could see it becoming very irritating in some cases, but potentially helpful in others. I would be fairly confident that it would at least catch some bugs in user code. But I agree that it's a tough call, and I think the current approach is reasonable.

@aleksey-bykov

In others cases (where taking a value at index implies a possibility of hitting
undefined) can't you just declare it number?[] rather than number[]?

This is how you should declare arrays in the first place in case you care. Something like string[] can never be a thing.

@dallonf
dallonf commented Mar 5, 2016

This is probably too complicated, but there are a few cases where you can statically know an array access to have a defined result. For example...

const array = ["hello","world"]; // string[]
const nullableArray = ["hello", null]; // string?[]
for (const i=0; i<array.length; i++) {
  console.log(array[i].toUpperCase()); 
}

In this case, i cannot possibly be any value other than 0-array.length. It gets more complicated if i is defined with let or var though, since it might have been modified elsewhere in the scope.

@ahejlsberg
Member

Just pushed a new batch of commits. Core theme is separation of null and undefined as distinct types that are not assignable to each other. The initial implementation of this PR conflated the two, but that is an inaccurate representation of JavaScript's semantics in a number of cases and also precludes programming in a style that uses only one of the two (for example, the TypeScript compiler doesn't use null at all).

Latest changes include:

  • Make null and undefined distinct types and allow them to be used as type names.
  • The type T? is exactly the same as T | null | undefined.
  • No widening of null and undefined in strict null checking mode.
  • Best common supertype computed using non-nullable form of types.
  • Introduce new comparable relation (see #5517) that is used for equality operators and type assertions. A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. There was already a need for this new relation and nullable types add more justification. For example, given types A and B where one is a subtype of the other, it isn't possible to compare A to B? or A? to B without this change.
  • Optional parameters and properties now have undefined (but not null) added to their type. For example x?: T automatically becomes x?: T | undefined.
  • An expressionless return statement is treated as return undefined for type checking purposes.
  • undefined is included in the inferred type of a function that has expressionless return statements and/or a reachable end point (which implies returning undefined).
  • It is an error to have a reachable end point in a function with a type annotation that doesn't include undefined.
  • Type guards using ==, !=, ===, and !== to compare to null or undefined now accurately reflect JavaScript semantics (e.g. double-equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).
  • Improved type relationship error reporting for nullable types.
  • Several smaller issues fixed.

I will update the description of the PR accordingly as soon as possible.

@spion
spion commented Mar 6, 2016

About the arithmetic operators being exempt from null checks - seems like a significantly big hole / footgun.

Its borderline acceptable for addition and subtraction as it defaults to the identity element (a noop), though I'm pretty sure I can find examples where a noop is bad. But I'm definitely sure multiplication and especially division cause some significant bugs. Division e.g. 5 / null is bound to produce unwanted NaNs that are very, very hard to track as they continue to propagate through calculations :)

Other than that, huge, huge +1 from me.

@JsonFreeman

Nice! Do you need to do something similar below if target.flags & TypeFlags.Intersection && relation === comparableRelation? This would be the case where you use equality comparison on A & B and B & C?

Actually I just realized my suggestion might be a bit silly, since that would only be true if the actual values were A & B & C. So any two types would be comparable just by intersecting them.

@aleksey-bykov

Thumbs up! I thought I would never see this happening. Thank you for choosing to model the JavaScript type system the way it is.

@ahejlsberg, what is going to happen with the void type? There is no match to it in JavaScript. It's hard to see what it will be modeling. An alias to the Undefined type in a position of a return type of a function? As far as I remember according to the current spec void doesn't have a value but can be assigned null or undefined which are now distinct referrable types. In this perspective the void type looks more like an alias to Null | Undefined. I suspect that it can also remain an outlaw type that has its very own semantics that doesn't match anything in JavaScript, but in view of what you just said eliminating it altogether looks like a better alternative considering other changes that seem breaking anyway.

@aleksey-bykov

@dallonf consider

const array = ["hello","world"]; // string[]
const nullableArray = ["hello", null]; // string?[]
for (const i=0; i<array.length; i++) {
  ahChooExcuseMe();
  console.log(array[i].toUpperCase()); 
}
function ahChooExcuseMe () {
   if (Date().getTime() ÷ 2 > 0) {
      array.pop();
      array.pop();
   }
}

point is, array is mutable, you can only reliably reason about static (guaranteed immutable) values doing static analysis

@ahejlsberg ahejlsberg Removing 'T?' type notation (use 'T | null | undefined' instead)
b1bef15
@ahejlsberg
Member

Latest commit removes the T? notation per conclusion from discussion in #7426. We may add it back at some point in the future if there is ever consensus as to what it should mean.

ahejlsberg added some commits Mar 14, 2016
@ahejlsberg ahejlsberg Remove 'T?' notation from type-to-string conversion
09ad9c5
@ahejlsberg ahejlsberg Merge branch 'master' into strictNullChecks
b497243
@jods4
jods4 commented Mar 14, 2016

I've read this page again but couldn't find information about the behaviour of any?

Because any could be null or undefined, is this going to be an error?
(<any>x).something()
Or does any disable all type checks including the new strict nulls?

My first intuition would be that any is opting out of typing so I should be allowed to do anything without compiler errors.

If you choose the other way, will there be a non-null any: let x: any! ?

@ahejlsberg
Member

@jods4 The behavior of any doesn't change. You can assign anything to an any and it effectively disables both strict and regular type checking.

@malibuzios

@jods4

You mentioned:

let x?: T on the other hand makes no sense. x is not optional at all, it is a declared variable and if you try to redeclare it you'll get an error.

First, I'm not sure what you exactly meant because re-declaring an optional function parameter would also yield an error:

function func(x?: number) {
  let x: string; // <-- Error: duplicate identifier
}

In any case, I am one of those people who never actually felt any problem with the let x?: T notation and even proposed and tried to promote it as a valid candidate. I can accept it is to some degree an analogy and does not represent a "complete" form of equivalence with the existing call signature and interface notation.

We need to separate two different scenarios where a value (or lack of it) passed to a property or call signature parameter having x?: T may be accepted, here's a basic example scenario:

function func(a: string, x?: number) {
...
}

interface MyInterface {
  a: string;
  x?: number;
}

The two main scenarios are:
(1). A value for x is not provided at all thus x is implicitly undefined. E.g:

func("hey");
let obj: MyInterface = { a: string }

(2). A value for x is provided but set to undefined had thus x is explicitly undefined. E.g.:

func("hey", undefined);
let obj: MyInterface = { a: string, x: undefined }

In terms of program execution, there is very little difference between these two cases. The only places a difference may surface is when enumerating function arguments such as with the arguments object or in the case of object, iterating over object properties with for..in or Object.keys etc. where the property would appear but its value would be explicitly undefined. Other than that, the run-time semantics should be exactly the same.

It makes sense for me that x?: T notation should include cases where the value was explicitly passed as undefined, I mean, even in a stricter mode, thus having let x?: T, which as you mentioned would always be, in practice, limited to the "explicitly undefined" semantics (to clarify: I did not mean the programmer would actually need to write let x?: number = undefined, of course), which I feel is reasonable, and is a compromise I'm willing to take, if only to ensure (relative) consistency in the notation and conservatively "recycle" the existing syntax in a way I feel is reasonably intuitive.

I'm actually quite surprised some people find this particular instance of the x?: T notation to be not as good or at least as reasonable as it may seem to me, and would be interested in hearing more of their views on the subject.

@jods4
jods4 commented Mar 17, 2016

@malibuzios
I certainly did not lay out my ideas in the best possible way but I think you understood me well.

In itself the notation is not as much of an issue as the larger context of how the language presents itself to users, in particular newcomers.

Syntactical sugar such as let x?: T being shorthand for let x: T | undefined shapes the way the language will be used. Even more so without alternatives such as let x: T?.
A good language would naturally lead beginners towards the path of success and I'm not sure this is it.

Given that it is not decided yet what good usage of "nullables" is (see the heat in #7426); I think it's wise to wait before adding any syntax. Once added it can't be removed. Depending on how things go I think we could also consider alternatives to that syntax.

If you ask me, let x?: T is completely unnecessary and adds useless complexity to the language and confusion for beginners. But of course that's just me ;)

@malibuzios

@jods4
I can understand different people may have different views and if there is no consensus, then it is usually wiser to wait until a later stage, where perhaps more insight would be accumulated over real-world cases and usage.

I actually made a special effort to express my my opinion that T? would become less intuitive if the question mark is used to denote something else than optionality (i.e. denoting T | null or T | null | undefined), which I felt may become a bit confusing for beginners, and would feel redundant if it was set to T | undefined - that's why I said I preferred nothing at all.

The let x?: T case was actually one of those places where I did not predict would become very controversial. It turns out it is, to some degree, and even some Microsoft members have expressed some doubts about it (see these design meeting notes under "optional members on new members"):

Variables are more of a questionable (pun intended) case - not entirely certain.

  • It seems like this would actually give you a case to say that your variable can be undefined and reflects that in the type.

Maybe I'm not seeing it broadly enough, but it seems there are many places in my code where when passing a value to a function which has an optional parameter, it would be natural to write something like:

function readSomeData(offset?: number)
  ..
}

function readMore() {
  let myOffset?: number;

  if (someConditionIsTrue())
    myOffset = getOffset(...);

  return readSomeData(myOffset)
}

I view the question mark in let myOffset?: number; as expressing the fact that myOffset may not be assigned at all (unlike the same notation in a class member, it is guranteed to exist - meaning in practice it is 'explicitly' set to undefined, despite the fact it may be never assigned). Think about it, even if it was, say, nulllable, it would still need to be assigned in order to have a value null. That's why this is mostly an analogy, not an exact parallel to the same notation in function parameters and interfaces/classes.

A roughly similar sort of symmetry may be seen between interfaces and implementing classes:

interface MyInterface {
  x?: number;
}

class MyClass implements MyInterface {
  x?: number;
}
@jods4
jods4 commented Mar 17, 2016

@malibuzios I don't know. I find that T? conveys well the fact that the T value is optional. It's also convenient that it can be used everywhere a type is used (return values, generic parameters, etc.) -- what it should compile to seems highly controversial though.

Your readSomeData() example is correct but in practice I think optional parameters are most commonly omitted rather than explicitly passed as undefined.
In my opinion that makes your example an unusual case where the syntax let x: number | undefined would be acceptable. It seems too corner case to justify added complexity in the language.

Another point that I don't like with let x? is that when TS 2.0 comes out with strict nulls, the compiler will report non-definite assignements of variables. Like:

function f() {
  let x: number;
  if (blah) x = 3;
  // more blah
  return x; // <-- error: use of unassigned x variable.
}

This is a good thing and likely to catch bugs. Having let x? encourages you to opt out of this.

@Raynos
Raynos commented Mar 18, 2016

The implementation on this branch does not support getOrCreate() pattern yet.

The following logically correct program fails with a type error

class OutPendingBucket {
    elements: Array<number>;
    count: number;

    constructor(elems:Array<number>) {
        this.elements = elems;
        this.count = 0;
    }
}

class OutPending {
    buckets: { [id: string] : OutPendingBucket | undefined };
    emptyBucket: Array<number>;
    bucketSize: number;

    constructor() {
        this.buckets = Object.create(null);
        this.bucketSize = 1024;

        this.emptyBucket = [];
        for (var i = 0; i < this.bucketSize; i++) {
            this.emptyBucket.push(0);
        }
    }

    getOrCreateBucket(bucketStart:string) : OutPendingBucket {
        var bucket = this.buckets[bucketStart];
        if (!bucket) {
            var elems = this.emptyBucket.slice(0, this.emptyBucket.length);
            bucket = this.buckets[bucketStart] = new OutPendingBucket(elems);
        }

        return bucket;
    }
}
raynos at raynos-Dell-Precision-M3800  ~/projects/TypeScript on strictNullChecks*
$ node built/local/tsc.js --strictNullChecks test.ts
test.ts(34,16): error TS2322: Type 'OutPendingBucket | undefined' is not assignable to type 'OutPendingBucket'.
  Type 'undefined' is not assignable to type 'OutPendingBucket'.

It complains that bucket could be undefined even though I have a undefined check and re-assign it to a non-nullable type.

I think this shouldn't be too hard to implement but becomes harder if:

  • You are assigning to this.bucket = X and return this.bucket;
  • If you alias; aka this.bucket = X; var self = this; return self.bucket
  • If you do nested fields in data structures and nested aliases...
@jods4
jods4 commented Mar 18, 2016

@Raynos
Your code requires flow analysis to determine that although bucket is nullable, it was definitely assigned a non-null value on every code path.

From what @ahejlsberg has written I think this is planned but not done yet.

In the meantime you can indicate to the compiler that you know bucket is not null by using:
return bucket!;

ahejlsberg added some commits Mar 21, 2016
@ahejlsberg ahejlsberg Merge branch 'master' into strictNullChecks
Conflicts:
	src/compiler/diagnosticMessages.json
	src/compiler/types.ts
	tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols
	tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols
413d9a6
@ahejlsberg ahejlsberg Accepting new baselines
fb6255a
@ahejlsberg ahejlsberg merged commit 3853bb8 into master Mar 21, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details
@ahejlsberg
Member

We've merged the PR with the work as it currently stands so people can experiment with it in the nightly builds.

_NOTE: The --strictNullChecks compiler option should be considered experimental at this point._

We are working on a separate PR for control flow based type analysis (see #2388) that will allow us to better track definite assignment and type guards in all forms. The --strictNullChecks feature isn't really complete until that work is done.

@Arnavion
Contributor

@zhengbli Is there any plan to get https://github.com/Microsoft/TSJS-lib-generator to generate | null / | undefined automatically?

@mhegazy mhegazy referenced this pull request in Microsoft/TSJS-lib-generator Mar 22, 2016
Closed

Add null as a type to the generated .d.ts files #81

@Arnavion
Contributor

Thanks! And for the non-generated .d.ts files, will you be taking PRs? Or are you already working on them?

@Arnavion
Contributor

core.d.ts and es6.d.ts at #7650

@myitcv
myitcv commented Mar 24, 2016

Zero values

When it comes to primitives (number, string, boolean) I had hoped this PR would introduce the concept of zero values, specifically 0 for number, "" for string and false for boolean. Consider:

let x: number; 
console.log(x); 

As it is, the above fails to compile (as documented) and requires that I do:

let x: number = 0;  // the type annotation is optional
console.log(x); 

Introducing the concept of zero values would mean that the following:

let x: number;

gets transpiled to:

// JS result of transpilation
var x = 0;

making the original example valid again:

let x: number; 
console.log(x);  // ok; outputs 0

Non-primitives

I'm really struggling to see the benefit of non-nullable types for any type that is not a primitive. Largely because at runtime all variables are in fact pointers (with both undefined and null being the uninitialised pointer values)

If we were to:

  • introduce the concept of a struct type
  • split the concept of an array into an array and slice (borrowing from the Go terminology)
  • adopt the same value semantics for structs and arrays as Go

then I would think differently, but that's an entirely different proposal to what is being discussed here. Indeed has such a proposal been discussed before?

@jods4
jods4 commented Mar 24, 2016

@myitcv
Sorry but I strongly disagree with introducing "default" initialization of values.

(1) because using an uninitialized variable can easily be hiding a bug and "defaulting" to some arbitrary value like 0 is very likely not to fix it.

let x: number; 
switch (size) {
  // forgot something...
//case "S": x = 1; break;
  case "M": x = 5; break;
  case "L": x = 10; break;
}
doSomethingWith(x);

I much prefer that the compiler gives me an error and tells me x might be uninitialized when calling doSomethingWith(x) rather than happily assuming I meant x = 0, which is likely to be incorrect.
Most statically typed languages require you to initialize your variables explicitely for this reason.

(2) because JS will behave differently from TS and this is unexpected.
In TS let x: number will compile to let x = 0
but plain JS let x actually means let x = undefined

(3) because TS can't provide a good default for non-nullable objects.
To what could possibly let x: ComplexType compile to?
This would be an inconsistency in the language, as JS doesn't make much difference between "references" and "values" like C# does...

(4) because there's no good default for union types, e.g. let x: number | boolean

(5) If you are trying to save keystrokes, it's even shorter to declare let x = 0 rather than let x: number!

I'm really struggling to see the benefit of non-nullable types for any type that is not a primitive

The benefit is huge! Guarantee at compile-time that your code is free of null-derefencing errors.
Catching stuff like that:

function fullname(p: Person) {
  // expects a non-null p, otherwise lastName.toUpperCase will crash
  return `${p.firstName} ${p.lastName.toUpperCase()}`;  
}

let me: Person;
// Some random logic that might leave me = null
if (whatever) me = null;
// TS will warn you that you have a potential bug right here,
// because you might pass a null value to a method the doesn't support it
fullname(me);

Having null without static typing has been called the "billion dollars mistake".
Many recent modern languages have been designed with null safety (Rust, Swift, Kotlin, ...).
Some older languages such as C# try to retrofit non-nullable references.

Also: documentation when you call other people's code.

@myitcv
myitcv commented Mar 28, 2016

@jods4

because using an uninitialized variable can easily be hiding a bug and "defaulting" to some arbitrary value like 0 is very likely not to fix it.

0 is not an arbitrary value. In Go, which is my primary reference here, the zero value has well defined meaning. And of course if something is well defined, then bugs occur, just as you point out, by someone not being familiar with the documented behaviour. It's just in this situation I would be advocating a different documented behaviour.

because JS will behave differently from TS and this is unexpected

I think by virtue of us commenting on a TypeScript forum, you and I have in some (or many) way(s) found Javascript and its specification to be lacking as a language in which to program at scale. So I find the argument "because that's what we expect from Javascript" to be somewhat lacking. Agreed, this is a departure from current behaviour, but if it's well defined and documented it doesn't matter per se.

because TS can't provide a good default for non-nullable objects

I pick up on this under the heading "Non-primitives". If a struct-like concept is introduced then that does have a well defined zero value. The same would be the case for an array (vs a slice)

because there's no good default for union types

Good point; Go does not have union types hence this does not arise.

If you are trying to save keystrokes

This is not my goal.

The benefit is huge! Guarantee at compile-time...

Sorry, it was a poor choice of words. I see the compile time benefits... and I certainly won't argue against any attempt to improve matters at compile time but at runtime (think interfacing with someone else's library) you have no guarantees that the variable you thought was a non-nullable number will not be null, or even undefined.

Consider the following library that I have written:

export function MyFunc(): number;

If you use my library and compile with --strictNullChecks then, as a user of my library, you are probably expecting that the result of MyFunc() will be a non-nullable number.

But I didn't write my library with --strictNullChecks - so in fact at runtime there are occasions when MyFunc() returns undefined, and sometimes null.

How do you catch this situation?
How do you handle it?
Surely you have to rewrite all library definitions along the following lines:

export function MyFunc(): number | undefined | null;

My unease comes from a compile time assertion that cannot be guaranteed to be true at runtime. The discussion about zero-values was an attempt to initiate a discussion about transpilation process that does help to guarantee true non-nullable values at runtime. I can see now I didn't make that particularly clear.

It's been quite an active discussion so apologies if I've missed something in the proposal/comments that covers this...

@jods4
jods4 commented Mar 29, 2016

0 is not an arbitrary value. In Go, which is my primary reference here, the zero value has well defined meaning.

C# uses the same convention for fields of structs or classes (although you have to explicitly initialize variables).
That doesn't make 0 less arbitrary. It is a value that the language has chosen for you and it does not necessarily mean something for your domain. The language could have set everything to -1 and it would have been just the same.

So I find the argument "because that's what we expect from Javascript" to be somewhat lacking.

Except TS follows JS specs even where they are pretty bad (e.g. ASI). Because one scenario that TS supports is taking your existing JS code as-is and compile it with TS.
So I don't think following the JS standard is a "lacking" argument. In fact, I think you need a compelling reason, not to.
Your suggestion also implies a change of behaviour when turning on or off the strictNullChecks compiler flag. I think that's a big no.

// Without --strictNullChecks
let x: number;
assert(x === undefined);

// With --strictNullChecks
let x: number;
assert(x === 0);

This is not my goal

What is it then? Most languages that I know of require definite assignment of local variables, because it's safer.

but at runtime (think interfacing with someone else's library) you have no guarantees that the variable you thought was a non-nullable number will not be null, or even undefined.

Depends on context.

  • Here we work on large LOB applications so all our code is checked -> foolproof.
  • Consuming external 3rd party library should also be foolproof. If API says function x(): number do you code defensively in case it returns a string? We don't and null is now no different from string. If it does return null when it says it doesn't then it's a bug in the library definition (or code) and it should be fixed...
  • If I wrote a library for others to use I would certainly be more defensive and do some early arguments check and report error if I got a null value where I expected something else.
@myitcv
myitcv commented Mar 29, 2016

That doesn't make 0 less arbitrary. It is a value that the language has chosen for you and it does not necessarily mean something for your domain. The language could have set everything to -1 and it would have been just the same.

Ok, I'll accept that. My reference to Go (and other languages that chose 0 as the default value for an int equivalent) was to cite prior art that people may be familiar with; I was seeking to use that prior at as an example. And it is the existence of such prior art that leads me to the "arbitrary" value of 0. I don't see this as a critical point of debate in all of this.

Except TS follows JS specs even where they are pretty bad

Let's agree this is a decision that has been taken by someone.

Because one scenario that TS supports is taking your existing JS code as-is and compile it with TS.

....as is this...

In fact, I think you need a compelling reason, not to.

This is your opinion; this might well also be the opinion (and decision) of the language designers. I'm not seeking to try and changes decisions... rather have a discussion around what I see as the main issues around non-nullable types.

This is not my goal

What is it then?

To make TypeScript:

  • easier to write
  • easier to read
  • easier to maintain

In the case of this PR/discussion, I'm hoping to make the runtime safer.

Most languages that I know of require definite assignment of local variables, because it's safer.

I've cited Go as an example where this isn't the case, because of the existence of the zero value concept. I don't cite it as an example to argue "hence we should do it this way", rather I do so as an example of prior art: such a decision, like all others, has benefits and drawbacks.

but at runtime (think interfacing with someone else's library) you have no guarantees that the variable you thought was a non-nullable number will not be null, or even undefined.

Depends on context.

Unless I've missed something in the Javascript VM spec, it does not depend on context. There is nothing in the runtime that guarantees this. I'm strictly talking about the runtime here...

  • Here we work on large LOB applications so all our code is checked -> foolproof.

What do you mean by foolproof?

  • Consuming external 3rd party library should also be foolproof...

Again, not sure what you mean by foolproof here... At compile-time? Runtime safe?

...If API says function x(): number do you code defensively in case it returns a string?

I think you slightly misconstrued my example. In the pre---strictNullChecks world, would you agree it is valid, according to the compiler, for a function defined as follows to return null or even undefined?

export function MyFunc(): number;

The null and undefined types are both sub-types of number. So now you have to know whether the library was written with --strictNullChecks in mind (not forgetting this could simply be a type definition for a Javascript library).

Do we code defensively in case it returns a string? No.
Does this worry me? Yes, because again at runtime we don't have type information available (for checking) at runtime.
How much of a problem is it in reality? I can't tell you with any degree of accuracy...

... If it does return null when it says it doesn't then it's a bug in the library definition (or code) and it should be fixed

Why is it a bug?

If I wrote a library for others to use I would certainly be more defensive and do some early arguments check and report error if I got a null value where I expected something else.

Why would you perform these checks? Earlier you said:

If API says function x(): number do you code defensively in case it returns a string? We don't and null is now no different from string.

So why check the arguments that your library has been passed?

I would posit that you will do so (and I agree it is sensible to do so) because you don't have these runtime guarantees... and because there is this question of whether the caller is using --strictNullChecks or not.

@jods4
jods4 commented Mar 29, 2016

In the case of this PR/discussion, I'm hoping to make the runtime safer.

I'd argue that it is safer to have an error at compile time than an arbitrary value at runtime (even if the value is well-defined).
I am not speaking of cases where you used the "uninitialized" value on purpose, but cases where you did so by mistake.
If you want to use a variable with 0 default value, just initialize it. Your intent will be clearer for readers of your code.

What do you mean by foolproof?

I mean that the situation where the variable is undefined or null cannot happen at runtime and this property was proven by the compiler.

So now you have to know whether the library was written with --strictNullChecks in mind (not forgetting this could simply be a type definition for a Javascript library).

From the PR description, .d.ts are only usable in strictNullChecks mode if they have been marked as such. There will be no ambiguity whether a function can return null or not.

Why is it a bug?

Because when a contract say you don't return null and you do, something is wrong (either the contract or the implementation). I think you would agree that if function x(): number returns a string, there is a bug somewhere?

So why check the arguments that your library has been passed?

Because consumers of my library may not read the doc and not use TypeScript, or not use strictNullChecks... In public APIs it's good practice to error as early as possible, even (especially?) if the usage is invalid.
For internal projects we rely on the compiler to do those checks.

@myitcv
myitcv commented Mar 29, 2016

What do you mean by foolproof?

I mean that the situation where the variable is undefined or null cannot happen at runtime and this property was proven by the compiler.

The compiler cannot, to my understanding, guarantee anything about the runtime. For a subset of situations where the only code executed at runtime is that which was compiled by a tsc compiler with --strictNullChecks enabled (or some equivalent checker, Flow?), then, yes, you enjoy certain assurances/comfort (but not guarantees, because the runtime is specified to allow this). But in general (read using external libraries) you don't have these assurances.

From the PR description, .d.ts are only usable in strictNullChecks mode if they have been marked as such.

From re-reading the introduction to this PR, it appears the pragma work is being "considered" and is therefore to my mind still outstanding (and not definite at that). Is there an update on this that I missed?

Because when a contract say you don't return null and you do, something is wrong (either the contract or the implementation). I think you would agree that if function x(): number returns a string, there is a bug somewhere?

We are talking at cross purposes here. In the pre---strictNullChecks world, it is entirely valid for something defined to return a number to return null or undefined. You are talking about a world in which the aforementioned pragma exists - in which case, yes, it would be a 'bug' for a function defined to return number, where the .d.ts is marked as --strictNullChecks, to return undefined or null. Notwithstanding the runtime issues that I consider the main issue.

Because consumers of my library...

Why should a different rule exist for consumers of a library? Arguably they have even less control over the code they are consuming. Unless you are consuming a library that falls into the category above: i.e. all code in, referenced and used by the library is compiled by tsc with --strictNullChecks turned on etc.

I think this comes down to not having runtime guarantees about the values a variable (or property in the case of an object) can take, or at least transpiling runtime checks that help assert such things.

@spion
spion commented Mar 29, 2016

FWIW I strongly oppose any automatic implicit default values. In the worst case I would require that they are explicitly specified by the user for every variable.

The proposal by @myitcv introduces surprising runtime behavior. Its not TypeScript's job to change JavaScript's runtime semantics.

The compiler cannot, to my understanding, guarantee anything about the runtime.

It can for example not accept variable declarations for non-undefined types that don't have an initializer, which would be infinitely better than just assigning some random value implicitly without the programmer knowing it.

Especially not when they want to change from no strict null checks to --strictNullChecks. I would never ever want implicit behavior in that case. I would instead want to review all instances of uninitialized variables on a case by case basis and I want the compiler to point them out to me.

Implicit assignment would mean

  1. the compiler not pointing them out and
  2. implicit bugs.

which is the worst possible outcome.

@myitcv
myitcv commented Mar 29, 2016

Ok, I'm going to separate out this point about zero values and the point about runtime guarantees because you make a good point.

The proposal by @myitcv introduces surprising runtime behavior.

It is not surprising if it is well documented. However the end of this reply...

Its not TypeScript's job to change JavaScript's runtime semantics.

We can't change the semantics you're right: we have to work with them. This is exactly my point about not having any runtime guarantees about values.

random value implicitly without the programmer knowing it.

The value is not random... it's well defined. Again, see the end of this reply.

Especially not when they want to change from no strict null checks to --strictNullChecks

@spion I very much take your point. This is a strong argument against introducing zero values. If zero values were well defined from the start of TypeScript then we would be in a very different world. But we are not in that different world. Hence I totally concede, they should not be introduced. Thanks for the discussion @jods4 @spion

So I believe my outstanding questions/concerns relate to runtime assertions about non-null values, and the aforementioned pragma.

@ahejlsberg
Member

For those of you interested, the PR for control flow based type checking is now available: #8010.

@mhegazy mhegazy deleted the strictNullChecks branch Apr 11, 2016
@ahejlsberg ahejlsberg added this to the TypeScript 2.0 milestone Apr 25, 2016
@electricessence

Wow. Basically code contracts built in!!! LOVE YOU GUYS!!!

@dashaus
dashaus commented Apr 30, 2016 edited

The following code (only with strictNullChecks enabled) raises error:

var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (var [key, value] of myMap) {
  alert(key + " = " + value);
}

It is a bug?

@ahejlsberg
Member

@dashaus That looks like a bug. BTW, it only repros with --strictNullChecks and --target es6.

@dokidokivisual dokidokivisual added a commit to karen-irc/karen that referenced this pull request May 4, 2016
@dokidokivisual dokidokivisual Auto merge of #604 - saneyuki:nullable, r=saneyuki
chore(TypeScript): Enable 'strictNullChecks' option

This tries to enable [`--strictNullChecks` option](Microsoft/TypeScript#7140) of TypeScript compiler.

- [Non-nullable types by ahejlsberg · Pull Request #7140 · Microsoft/TypeScript](Microsoft/TypeScript#7140)
  - [Non-strict type checking · Issue #7489 · Microsoft/TypeScript](Microsoft/TypeScript#7489)
  - [[Request for feedback] Nullable types, `null` and `undefined` · Issue #7426 · Microsoft/TypeScript](Microsoft/TypeScript#7426)
- [Control flow based type analysis by ahejlsberg · Pull Request #8010 · Microsoft/TypeScript](Microsoft/TypeScript#8010)

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/karen-irc/karen/604)
<!-- Reviewable:end -->
25ed88b
@rajington rajington added a commit to rajington/flow-vs-typescript that referenced this pull request Jun 4, 2016
@rajington rajington Add note about TypeScript 2 support for non-null
Despite being a fan of Flow, I do think it's prudent for any recent comparison to include SOME features on the project timeline, including [non-nullable types](Microsoft/TypeScript#7140) in version 2.
1517f63
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment