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

Suggestion: non-nullable type #185

Closed
fdecampredon opened this Issue Jul 22, 2014 · 358 comments

Comments

Projects
None yet
@fdecampredon
Copy link

fdecampredon commented Jul 22, 2014

Introduce two new syntax for type declaration based on JSDoc

var myString: !string = 'hello world'; //non-nullable
var myString1: ?string = 'hello world'; // nullable 
var myString2: string = 'hello world'; // nullable 
var myString3 = 'hello world'; // nullable

by default type are nullable.

Two new compiler flag :

  • inferNonNullableType make the compiler infer non-nullable type :
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (I guess there might be a better name) :
var myString: !string = 'hello world'; // non-nullable
var myString1: string = 'hello world'; // non-nullable 
var myString2: ?string = 'hello world'; // nullable 
var myString3 = 'hello world' // non-nullable
@electricessence

This comment has been minimized.

Copy link

electricessence commented Aug 19, 2014

I suggest using a type other than string as an example since it by nature is nullable. :P
I can perceive non-nullable types being problematic since the user and compiler of "!" expects the type to always be non-null, which can never be truly asserted in JavaScript. A user might define something like this:

function(myNonNull:!myClass):void {
  myNonNull.foo();
}

And because it's defined as non-null, everything might be happy for the compiler, but then someone else who uses it in javascript passes something null and kaboom.

Now that said, maybe the solution could be that for public facing methods, it could automatically assert not null. But then the compiler could also assert that you cannot have public properties (or private for that matter really) that can have a !nonnull declaration since they could not be enforced.

This may go deeper into the discussion of code contracts for this to be properly enforced.

@aleksey-bykov

This comment has been minimized.

Copy link

aleksey-bykov commented Aug 19, 2014

Forgive my critics, I think there is very little need in non-nullable types if/as-soon-as algebraic data types are here. The reason people use null to represent a missing value is because there is no better way to do that in JavaScript and in most OOP languages alike. So imaging ADTs are already here. Then, as for the old libs written before non-nullables, having them won't make life any better. As for the new libs, with ADT's in place one can very accurately model what a value can take according to the business domain specification without using nulls at all. So I guess what I am saying is that ADT is way more powerful tool to address the same problem.

@samwgoldman

This comment has been minimized.

Copy link

samwgoldman commented Aug 19, 2014

Personally, I just wrote a little Maybe<T> interface and use discipline to ensure that variables of that type are never null.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Aug 20, 2014

I suggest using a type other than string as an example since it by nature is nullable. :P
I can perceive non-nullable types being problematic since the user and compiler of "!" expects the type to always be non-null, which can never be truly asserted in JavaScript. A user might define something like this:

function(myNonNull:!myClass):void {
myNonNull.foo();
}
And because it's defined as non-null, everything might be happy for the compiler, but then someone else who uses it in javascript passes something null and kaboom.

I don't really understand you can also define :

function myFunc(str: string): int {
 return str && str.length;
}

and if someone pass an int to that function it will ends up with an error also, an advantage of typescript is to delegate to the compiler pass things that you would check manually in javascript, having another check for nullable/non-nullable type seems reasonable for me. By the way SaferTypeScript and ClosureCompiler already do that sort of check.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Oct 20, 2014

With union types, we could have a pretty simpler specification for that.
Let's say we have now a basic type 'null', we can have a 'stricter' mode where 'null' and 'undefined' is not compatible with any type, so if we want to express a nullable value we would do :

var myNullableString: (null | string);
var myString = "hello";
myNullableString = myString //valid
myString = myNullableString // error null is not assignable to string;

With the 'strict mode' activated typescript should check that every variable non nullable is initialized, also by default optional parameter are nullable.

var myString: string; // error
var myNullableString: (null | string); // no error

function myString(param1: string, param2?: string) {
  // param1 is string
  // param2 is (null | string)
}
@robertknight

This comment has been minimized.

Copy link

robertknight commented Oct 21, 2014

@fdecampredon +1

IIRC from what Facebook showed of Flow which is using TypeScript syntax but with non-nullable types by default they support a shorthand for (null | T) as in your original post - I think it was ?T or T?.

@robertknight

This comment has been minimized.

Copy link

robertknight commented Oct 21, 2014

var myString: string; // error

That could potentially be quite annoying in the case where you want to initialize a variable conditionally, eg.:

var myString: string;
if (x) {
myString = a;
} else if (y) {
myString = b;
} else {
myString = c;
}

In Rust for example, this is fine as long as the compiler can see that myString will get initialized before it is used but TypeScript's inference doesn't support this at the moment.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Oct 21, 2014

Honestly doing something like var myString = '' instead of var myString: string does not bother me so much, but sure that kind of rule is always possible.

@johnnyreilly

This comment has been minimized.

Copy link

johnnyreilly commented Oct 21, 2014

@fdecampredon +1 for this - I like the idea very much. For code bases that are 100% JavaScript this would be a useful compile-time only constraint. (As I understand your proposal there's no intention for generated code to enforce this?)

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Oct 21, 2014

As for shorthand for (null | string) sure ?string is fine.
And sure @johnnyreilly it's only a compile time check

@samwgoldman

This comment has been minimized.

Copy link

samwgoldman commented Oct 25, 2014

Sum types make non-nullable types by default a very interesting possibility. The safety properties of non-nullable by default can't be overstated. Sum types plus the planned "if/typeof destructuring" (not sure what this should be called) even make it type safe to integrate nullable and non-nullable APIs.

However, making types non-nullable by default is a huge breaking change, which would require changing almost every existing third-party type definition file. While I am 100% for the breaking change, no one person is able to update the type definitions that are out there in the wild.

It's good that a great consensus of these definitions are collected in the DefinitelyTyped repo, but I still have practical concerns about this feature.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Oct 26, 2014

@samwgoldman the idea is to have non-nullable types only under a special compiler flag like nonImplicitAny this flag could be named strict or nonNullableType. So there would be no breaking changes.

@samwgoldman

This comment has been minimized.

Copy link

samwgoldman commented Oct 26, 2014

@fdecampredon What about the type definitions for non-TypeScript libraries, like those at DefinitelyTyped? Those definitions are not checked by the compiler, so any 3rd party code that could return null would need to be re-annotated in order to work correctly.

I can imagine a type definition for a function that is currently annotated as "returns string," but sometimes returns null. If I depended on that function in my nonNullableType'ed code, the compiler doesn't complain (how could it?) and my code is no longer null-safe.

Unless I'm missing something, I don't think this is functionality that can be turned on and off with a flag. It seems to me that this is an all-or-nothing semantic change to ensure interoperability. I would be happy to be proven wrong, though, because I think a flag-switched feature is more likely to happen.

As an aside, there isn't much information available yet on Facebook's Flow compiler, but from the video recording of the presentation, it seems like they went with non-nullable by default. If so, at least there is some precedence here.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Oct 26, 2014

Ok let's assume there is a shorthand ? type for type | null | undefined.

@fdecampredon What about the type definitions for non-TypeScript libraries, like those at DefinitelyTyped? Those definitions are not checked by the compiler, so any 3rd party code that could return null would need to be re-annotated in order to work correctly.

I can imagine a type definition for a function that is currently annotated as "returns string," but sometimes returns null. If I depended on that function in my nonNullableType'ed code, the compiler doesn't complain (how could it?) and my code is no longer null-safe.

I don't see the problem, sure some definition files won't be valid with the nonNullableType mode, but most of the time good library avoid to return null or undefined so the definition will still be correct with majority of the cases.
Anyway I personally rarely can pick a DefinitelyTyped definition without having to check/modify it you'll just have a little bit of extra work to add a ? prefix with some definitions.

Unless I'm missing something, I don't think this is functionality that can be turned on and off with a flag. It seems to me that this is an all-or-nothing semantic change to ensure interoperability. I would be happy to be proven wrong, though, because I think a flag-switched feature is more likely to happen.

I don't see why we could not have a flag switched feature, the rules would be simple :

  • in normal mode ? string is equivalent to string and null or undefined are assignable to all the types
  • in nonNullableType mode ? string is equivalent to string | null | undefined and null or undefined are not assignable to any other type than null or undefined

Where is the incompatibility with a flag-switched feature ?

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Oct 26, 2014

Flags that change the semantics of a language are a dangerous thing. One problem is that the effects are potentially very non-local:

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

It's important that someone looking at a piece of code can "follow along" with the type system and understand the inferences that are being made. If we starting having a bunch of flags that change the rules of the language, this becomes impossible.

The only safe sort of thing to do is to keep the semantics of assignability the same and change what's an error vs what isn't depending on a flag, much like how noImplicitAny works today.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Nov 24, 2014

I know it would break retro-compatibility, an I understand @RyanCavanaugh point of view, but after tasting that with flowtype it is honestly really an invaluable feature, I hope it will ends up being a part of typescript

@fletchsod-developer

This comment has been minimized.

Copy link

fletchsod-developer commented Nov 24, 2014

In addition to RyanCavanaugh's comment --> From what I read somewhere, the ES7 specification / proposal mention the use of function overloading (Same function name but different input parameter datatype). That is a very sorely needed feature for Javascript.

@NoelAbrahams

This comment has been minimized.

Copy link

NoelAbrahams commented Nov 24, 2014

From the flow docs:

Flow considers null to be a distinct value that is not part of any other type

var o = null;
print(o.x); // Error: Property cannot be accessed on possibly null value

Any type T can be made to include null (and the related value undefined) by writing ?T

var o: ?string = null;
print(o.length); // Error: Property cannot be accessed on possibly null or undefined value

[Flow] understands the effects of some dynamic type tests

(i.e. in TS lingo understands type guards)

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length); // Okay, because of the null check

Limitations

  • Checks on object properties are limited because of the possibility of aliasing:

    In addition to being able to adjust types of local variables, Flow can sometimes also adjust types of object properties, especially when there are no intermediate operations between a check and a use. In general, though, aliasing of objects limits the scope of this form of reasoning, since a check on an object property may be invalidated by a write to that property through an alias, and it is difficult for a static analysis to track aliases precisely

  • Type guard-style checks can be redundant for object properties.

[D]on't expect a nullable field to be recognized as non-null in some method because a null check is performed in some other method in your code, even when it is clear to you that the null check is sufficient for safety at run time (say, because you know that calls to the former method always follow calls to the latter method).

  • undefined is not checked.

Undefined values, just like null, can cause issues too. Unfortunately, undefined values are ubiquitous in JavaScript and it is hard to avoid them without severely affecting the usability of the language. For example, arrays can have holes for elements; object properties can be dynamically added and removed. Flow makes a tradeoff in this case: it detects undefined local variables and return values, but ignores the possibility of undefined resulting from object property and array element accesses

@spion

This comment has been minimized.

Copy link

spion commented Nov 25, 2014

What if the option is added at the same time when introducing the null type (and the questionmark shorthand)? The presence of a null type in a file would force the compiler into non-nullable mode for that file even if the flag is not present at the command line. Or is that a bit too magical?

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Dec 10, 2014

@jbondc seems good. however the problem with that is that it will ends up with ! everywhere :p

@misfo

This comment has been minimized.

Copy link

misfo commented Dec 10, 2014

It's tempting to want to change JavaScript but the reality is a 'string' is nullable or can be undefined.

What does this mean? There are no static types in js. So, yes, strings are "nullable", but let's not forget that they are also numberable and objectable and fooable, etc. Any value can have any type.

So when layering a static type system on top of javascript, choosing whether static types are nullable or not is just a design decision. It seems to me non-nullable types are a better default, because it's usually only in special cases that you want a function signature, for instance, to accept a null value in addition to the type specified.

@metaweta

This comment has been minimized.

Copy link

metaweta commented Dec 13, 2014

Directives like "use strict" that cause scoped changes to semantics are already a part of the language; I think it would be reasonable to have a "use nonnullable types" directive in TypeScript.

@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Dec 13, 2014

@metaweta I don't think it's enough, for example what happens if a non null module consume a nullable one :

//module A
export function getData(): string[] {
  return null;
}
//module B
'use nonnull'
import A = require('./A');

var data: string[] = A.getData();

data in module B is in fact nullable, but since 'use nonnull' was not used in module A should we report an error ?
I don't see a way to solve that problem with directive based feature.

@metaweta

This comment has been minimized.

Copy link

metaweta commented Dec 13, 2014

Yes,

var data: string[] = A.getData();

would cause an error. Instead, you'd have to provide a default value for when getData() returns null:

var data: string[] = A.getData() || [];
@fdecampredon

This comment has been minimized.

Copy link
Author

fdecampredon commented Dec 13, 2014

@metaweta ok but how do you know that it's an error ? :)
type of getData is still '() => string[]' would you automaticly treat everything that comes from a 'nullable module' as 'nullable ' ?

@metaweta

This comment has been minimized.

Copy link

metaweta commented Dec 13, 2014

Yes, exactly (unless a type from the nullable module is explicitly marked otherwise).

@andy-hanson

This comment has been minimized.

Copy link

andy-hanson commented Dec 31, 2015

I attempted to mitigate this problem with a special type Op<A> = A | NullType. It seems to work pretty well. See here.

@pietro909

This comment has been minimized.

Copy link

pietro909 commented Feb 25, 2016

+1 for --noImplicitNull as well PLEASE 👍

@rupebac

This comment has been minimized.

Copy link

rupebac commented Apr 1, 2016

+1 for --noImplicitNull

@Gaelan

This comment has been minimized.

Copy link

Gaelan commented Apr 3, 2016

Should this be closed?

@isiahmeadows

This comment has been minimized.

Copy link
Contributor

isiahmeadows commented Apr 4, 2016

@Gaelan Given #7140 is merged, if you would like to file a new, dedicated issue for --noImplicitNull as suggested by a few people here, then it's probably safe to do so now.

@Gaelan

This comment has been minimized.

Copy link

Gaelan commented Apr 4, 2016

@isiahmeadows It would probably be better to leave this open then.

@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented Apr 5, 2016

Should this be closed?

We think #2388 is the renaming part of this work. This is why we have not declared this feature complete yet.

if you would like to file a new, dedicated issue for --noImplicitNull as suggested by a few people here, then it's probably safe to do so now.

I am not sure i understand what is requested semantics of this new flag. i would recommend opening a new issue with a clear proposal.

@isiahmeadows

This comment has been minimized.

Copy link
Contributor

isiahmeadows commented Apr 5, 2016

@mhegazy The idea posited earlier in this issue for --noImplicitNull was that everything has to be explicitly ?Type or !Type. IMHO I don't feel it's worth the boilerplate when there's another flag that infers non-nullable by default that IIRC was already implemented when nullable types themselves were.

@ahejlsberg

This comment has been minimized.

Copy link
Member

ahejlsberg commented Apr 26, 2016

Closing now that #7140 and #8010 are both merged.

@ahejlsberg ahejlsberg closed this Apr 26, 2016

@ahejlsberg ahejlsberg added this to the TypeScript 2.0 milestone Apr 26, 2016

@mhegazy mhegazy added the Fixed label Apr 26, 2016

@massimiliano-mantione

This comment has been minimized.

Copy link

massimiliano-mantione commented May 17, 2016

Sorry if I comment on a closed issue but I don't know a better place where to ask and I don't think this is worth a new issue if there's no interest.
Would it be feasible to handle implicit null on a per-file basis?
Like, handle a bunch of td files with noImplicitNull (because they come from definitelytyped and were conceived that way) but handle my source as implicitNull?
Would anybody find this useful?

@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented May 17, 2016

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.