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

Non-nullable types #7140

Merged
merged 65 commits into from Mar 21, 2016

Conversation

Projects
None yet
@ahejlsberg
Member

ahejlsberg commented Feb 18, 2016

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.

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

This comment has been minimized.

Show comment
Hide comment
@RyanCavanaugh

RyanCavanaugh Feb 19, 2016

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

Member

RyanCavanaugh commented Feb 19, 2016

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

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Feb 19, 2016

Member

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

Member

ahejlsberg commented Feb 19, 2016

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

@robertknight

This comment has been minimized.

Show comment
Hide comment
@robertknight

robertknight Feb 19, 2016

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.

robertknight commented Feb 19, 2016

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.

@myitcv

This comment has been minimized.

Show comment
Hide comment
@myitcv

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

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

This comment has been minimized.

Show comment
Hide comment
@tejacques

tejacques Feb 19, 2016

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

tejacques commented Feb 19, 2016

👍 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

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Feb 19, 2016

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.

Member

ahejlsberg commented Feb 19, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@tejacques

tejacques Feb 19, 2016

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

tejacques commented Feb 19, 2016

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

This comment has been minimized.

Show comment
Hide comment
@aleksey-bykov

aleksey-bykov Feb 20, 2016

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

aleksey-bykov commented Feb 20, 2016

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

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Feb 20, 2016

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!

Contributor

JsonFreeman commented Feb 20, 2016

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

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Feb 20, 2016

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

Member

ahejlsberg commented Feb 20, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Feb 20, 2016

Contributor

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

Contributor

JsonFreeman commented Feb 20, 2016

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

@ivogabe

This comment has been minimized.

Show comment
Hide comment
@ivogabe

ivogabe Feb 20, 2016

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

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 added this to the TypeScript 2.0 milestone Apr 25, 2016

@electricessence

This comment has been minimized.

Show comment
Hide comment
@electricessence

electricessence Apr 26, 2016

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

electricessence commented Apr 26, 2016

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

@emilgpa

This comment has been minimized.

Show comment
Hide comment
@emilgpa

emilgpa Apr 30, 2016

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?

emilgpa commented Apr 30, 2016

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

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Apr 30, 2016

Member

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

Member

ahejlsberg commented Apr 30, 2016

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

@paavohuhtala paavohuhtala referenced this pull request May 1, 2016

Closed

external sql #16

dokidokivisual added a commit to karen-irc/karen that referenced this pull request May 4, 2016

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

rajington added a commit to rajington/flow-vs-typescript that referenced this pull request Jun 4, 2016

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.

@bradleyayers bradleyayers referenced this pull request Jun 8, 2016

Open

Add documentation for block nesting #9

0 of 3 tasks complete

@spalger spalger referenced this pull request Jun 21, 2016

Closed

Consider static types #115

@orta orta referenced this pull request Sep 26, 2017

Merged

Add typings for react-tracking #20011

4 of 4 tasks complete

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