TypeScript: Check non-undefined properties are initialized in the constructor with `--strictNullChecks`

class C {
   p: number; // Should be an error under --strictNullChecks
   method() {
    this.p; 
  }
}

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 92
  • Comments: 75 (27 by maintainers)

Commits related to this issue

Most upvoted comments

Reopening this issue. New plan, look into adding the check under a new flag (on by default under --strict).

Having looked at several code bases, I don’t think it is feasible to implement this feature as we have discussed it. There are simply too many common scenarios where users would need to turn the feature off if we insist that the constructor itself must initialize every non-nullable property. Effectively, it would become more of an irritant than a help.

Please re-consider adding this feature (default off and turned on with --noUninitializedClassProperties).

IMO this is a big potential source of errors, have stumbled on this issue only 4 days after switching to strict null checks (which is awesome btw).

Is this feature definitely not getting implemented then? I’ve been really excited about strictNullChecks since I first heard TS would introduce it, but for me this exception would pretty much be a deal breaker. I feel like a feature like this needs to be all or nothing. Other languages that deal with this, like Rust, are entirely consistent with their approach.

I also think that anyone who’s irritated by this will also be irritated enough in general about strict null checking, that they are likely not to use it anyway.

This suggestion is now implemented by #20075.

I think the “principle of least surprise” is a good rule to follow when evaluating these sorts of decisions. What behaviour are users likely to expect after reading about and choosing to opt in to strictNullChecks? When one learns of the ability to specify certain class properties as non-nullable, the most obvious conclusion to draw is that when accessing those properties, they will never be null or undefined.

The type checker should be consistent in its treatment of properties and variables with regards to non-nullability constraints (as it already is with other kinds of type constraints), while allowing escape hatches when needed, as explained further below.

According to [1], the no. 1 goal of TypeScript is “Statically identify constructs that are likely to be errors”. I would argue that a constructor that fails to initialize a non-nullable property is likely to be incorrect. That doesn’t mean it is incorrect - just that it’s likely to be.

@ahejlsberg made a good argument about the fact that there are some codebases in which the developers prefer to do some or all of the initialization outside of the constructor itself. That’s understandable, but I think there’s a simple solution to that:

As it stands right now, the compiler will happily accept the following code:

const foo: number = <any>undefined;

So for code where the user has opted into strictNullChecks but nonetheless wants to initialize class properties in a such a way that can’t be statically verified by the compiler to be non-null/undefined, then they could use the <any> escape hatch to do the same thing as above, which is essentially saying “trust me, I know what I’m doing here”. With variables, the compiler does not lie to you unless you explicitly tell it to, and I argue that properties should be handled in the same way.

As an example, if you wanted to initialize some properties after performing an asynchronous operation, you would do the following:

class Person {
    name: string;
    age: number;
    public constructor() {
        this.name = <any>undefined;
        this.age = <any>undefined;
        someAsyncFunction().then(() => this.init());
    }
    private init() {
        this.name = 'Fred';
        this.age = 30;
    }
}

In this code, the compiler would see that all the class properties are being initialized (albeit with an escape hatch, as is allowed for variables) and be satisfied. The real initialization can then be done separately. The user gets their way, and accepts the risks associated with this approach.

[1] https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

I’d also add that if the purpose of non-nullable types is to ensure that a value (be it an argument, variable, or property) can never be null, then the lack of such checks in constructors effectively defeats the purpose of the feature. Swift is very strict on this matter and IMHO gets it right - if you tell the compiler that the type of a given value cannot be null, you are guaranteed this will be the case.

Omitting such checks on properties at initialization time severely limits the utility of this feature, particularly given that it’s likely that potentially uninitialised properties can be assigned to values which have a non-nullable type, thus violating the guarantees that the type system supposedly guarantees.

In the following code, s will be given the (non-nullable) type string, but its actual value will be undefined.

class Person {
    public name: string;
}

const p = new Person();
const s = p.name;

I also strongly believe that not initializing variable in constructor should raise compiler error. If we would NOT want to initialize this variable, we can set it as x: number | undefined; , but more often a silent bug will just bypass the (otherwise) solid ts type system, when developer will forget to initialize variable in constructor.

Sorry to barge in with little more than a +1, but just wanted to keep this thread alive. I’m working on a very large project that was JS and is now TypeScript, and the strictNullChecks flag has been incredible for finding both existing bugs and preemptively detecting ones I didn’t know existed. If member variables were also checked I think that would be 99% of my runtime exceptions gone.

A huge win for TypeScript. Thank you so much for listening to feedback and for your efforts on this!!

I don’t know what to tell you. The initial implementation of strict null checks did require classes to initialize their properties in the constructor. As I said before, we wanted this to be the behavior and decided not to based on available data. We didn’t do the right thing and then hit our heads on the pavement and do the wrong thing instead just to garner more duplicate bugs on the issue tracker.

How many have come to see the issue and been opposed?

One thing about making a decision like this is that you don’t hear from the people who like the behavior. No one writes their intialization code in an init method and then hunts down a bug on an issue tracker to applaud the fact that it worked.

Unless I’m missing some data you’re deciding this by fiat rather than by data

Some data: If you want this behavior today, you can opt into it using the TSLint rule https://github.com/palantir/tslint/issues/1414 . Except that you can’t, because no one has considered it worth their time to submit the PR. TSLint has taken hundreds of PRs and zero of them have bothered to implement the check for this, even though someone already posted code for it.

Some data: You could also use flowtype, which is in general more sound, except that they don’t do this check either, despite “more soundness” being one of their primary selling points. A language that out of the gate had strict null checking didn’t bother to implement this check. The issue on their repo tracking this has been open for 2 years with 1 👍 and no comments.

Again: We wanted this to be the default behavior. It was the behavior, and we changed it, on purpose, based on data. We then reconsidered that data based on feedback, and again decided that there would be too many breaks, and that a flag would be a half-baked solution that didn’t truly solve the problem in a cohesive way. First five devs yelled at each other for several hours about what to do about this. Then, later, 14 people sat in a room for 45 minutes and yelled at each other about what the right way to address this was and came to the conclusion that, for now, the current design was the best option.

you’re deciding this by fiat … other arguments do not seem well supported … Or then would the goal post move again?

This comes off as really rude when we’ve been engaging in good faith here. The fact that you don’t like the outcome is not an indicator that the process is broken, unfair, or arbitrary. There are going to be decisions that don’t achieve 100% consensus. As I said, we are going to revisit this. The data does change and we are still looking at it. If the only outcome you’re willing to accept as legitimate is the one you agree with, then I cannot guarantee you satisfaction.

It’s not clear to me why this and all related issues are being closed as “not a bug”. The docs for --strictNullChecks currently state:

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…

And I think most people would interpret “only assignable” as including both explicit assignment (via code) and implicit assignment (by the JS engine since properties are undefined by default).

If only explicit assignment is the intended protection, that stinks a little but we’ll live with it - but please update the docs to describe the behavior since it’s currently unclear.

I’d be in favour of Swift’s approach. A constructor is required to initialise all non-nullable properties. The compiler checks all branches to make sure this happens in every case.

As a simple example, here’s a Swift class with a non-nullable field. It’s required to have a constructor, because otherwise there’s no way of initializing the field (this would not be the case if the type was String? instead of String). This gives the error Class ‘Person’ has no initializers:

class Person {
    var name: String;
}

If we add an init method, but don’t initialize the field, we get the following error: Return from initializer without initializing all stored properties

class Person {
    var name: String;
    init() {
    }
}

If we have an init method which initializes the field in some branches but not others, the same error is reported: Return from initializer without initializing all stored properties

class Person {
    var name: String;
    init(theName: String, setName: Bool) {
        if setName {
            name = theName;
        }
    }
}

The compiler thus enforces you to initialize it in all branches:

class Person {
    var name: String;
    init(theName: String, setName: Bool) {
        if setName {
            name = theName;
        }
        else {
            name = "Unknown";
        }
    }
}

I think strictNullChecks without this feature significantly ruins this feature of TypeScript for all practical purposes. You must be able to enforce the construction of “complete” types. This is a fundamentally valuable feature of strictNullChecks.

I agree with a complete initialization check of fields in constructor.

False positives pointed out in the comment (https://github.com/Microsoft/TypeScript/issues/8476#issuecomment-288198119) by @RyanCavanaugh can be solved by the language specification like Swift: any instance methods cannot be called until all instance fields are initialized. So deep flow analysis is not needed.

Technically, in the example code in the link above, overriding can cause problems by code like below. It is not undefined-safe actually and should be rejected by the compiler.

class Foo {
    alpha: string;
    constructor() {
        if (tuesday) {
            this.initTuesday();
        } else {
            this.initOtherday();
        }

        // alpha must not be `undefined` here
        console.log(this.alpha);
    }
    initTuesday() {
        this.alpha = 'tacos!';
    }
    initOtherday() {
        this.alpha = 'hello';
    }
}

class Bar extends Foo {
    initTuesday() { }
    initOtherday() { }
}

// console.log prints "undefined" unexpectedly.
new Bar();

In fact, the most important issue is not false positives (blocking safe code) but false negatives (passing unsafe codes). Null-unsafe code is allowed by the compiler at this moment. If we cannot prevent both at the same time perfectly, we should prefer the safer one: preventing false negatives and allow some false positives.

I understand that a lot of existing code gets compilation errors by introducing new checks. However that also happened when strictNullCheck was introduced. At that time, it did not cause problems to provide the way to control compatibilities by the strictNullCheck option.

This new initialization checks are same. It is consistent to provide the initialization check with an option to control the compatibilities. As a fact, we have already started providing the option, strictNullCheck, to write safer code even if there are code breaking changes.

I think it is better to unite the option for the initialization checks into strictNullCheck than prociding an independent option because it makes sense that strictNullCheck provides strict initialization null checks. Currently strictNullCheck is not actually strict and it makes it more strict.

@MicahZoltu Our initial assumption was that we should check for initialization. We turned this check on and ran it against our suite of internal partner code (several million LOC) and found that it had a lot of false positives. There were too many classes that deferred to other methods for initialization, or used Object.assign(this, ..., etc.

The other problem is that the behavior we can provide is not the behavior people want (as seen by the comment above yours). Nearly everyone would expect this code to check without error:

class Foo {
    alpha: string;
    constructor() {
        if (tuesday) {
            this.initTuesday();
        } else {
            this.initOtherday();
        }
    }
    initTuesday() {
        this.alpha = 'tacos!';
    }
    initOtherday() {
        this.alpha = 'hello';
    }
}

but we can’t actually spider out the entire control flow graph and ensure that all possible branches always initialize all variables (it’s easy to think of an algorithm that works for simple cases, but it’s also easy to construct sequences which are not analyzable).

This is tagged Revisit and will be something we look at again. With 700 other open suggestions I can’t promise it will be immediately, but we’re definitely aware of the volume of feedback.

Also this makes very little sense. Not counting this particular behaviour, the rest of TypeScript is “complete” in terms of 100% type safety. That means you cant really make a type mistake unless you read/use ‘any’ type, casting or use external library with invalid definitions file. I think TS should require either defining property in constructor or set is as type|undefined. Allowing a field that in terms of typechecking is never undefined, but it is on runtime makes no sense in such strict typed language.

Especially that we have tools to deal with these problems, like ? operator or just type | undefined.

I’m a pragmatist not a purist, and try to be disciplined about chasing edge cases. However addressing this would meaningfully improve consistency and error checking.

@ahejlsberg trust us on the irritant factor - we have experience with this. Over the last two years I’ve lived and watched developers of various skill levels ramping up on Swift. It’s an irritant for a short time until the concept clicks, then people become comfortable with the safer style of development.

A side benefit is Flow evangelists will stop beating TS over the head with this, but I do not weigh this in my suggestion.

We need every useful static error we can get, let’s bite the bullet and get one more on our side.

I have run into this twice since enabling strict null checks only a few days ago, and one case was on production (the bug was there before of course, but I expected that strict null checks would basically eliminate it).

I am totally fine with this being very irritating if it finally makes those undefined errors go away. Adding a flag that is off by default sounds like a great idea for anyone who does not have to support legacy code that uses patterns that conflict with this.

@serbanghita If you have an object pool, then there are well-defined times when you expect a property to be undefined - that is, between the constructor call and the completion of the init method. You should then define the property as being T|undefined, because there are times when that property is both known and expected to be undefined. The type system does not, can not, and should not know about your pattern’s lifecycle.

Yes, we might be able to do something for readonly members without it being an irritant (since we already only allow assignments in initializers or the constructor).

What is the status of this? I see that the issue was dropped from TS2.6 milestone recently. Is this because of some latest design decision or the status is just unknown?

(Apologies for the long post). Let me chime in with a different view here, regarding @RyanCavanaugh’s open question.

First, to clarify, I think there are three types of initialization we need to support:

  1. Vanilla: Every property is specified in the constructor
  2. Tuesday: This is the if (tuesday) { this.initTuesday() } else { this.initOther() } approach. By the time the constructor returns, the object will be completely initialized, but not every property is specified lexically in the constructor.
  3. Deferred: This is where partially-initialized objects are created to put into a pool, and only fully-initialized when they are ready to be used. Async initialization also falls in here. Note that a class can use both Tuesday and Deferred methods.

I think several of @RyanCavanaugh’s open questions could be resolved by extending slightly the existing type guard system. To keep it simple, let me describe the idea first for vanilla-initialized classes.

Mathematically, the constructor is dealing with multiple types for this: Initially this has type Partial<this>; then after super(...), it will have some type set by the superclass constructor, possibly leaving some additional properties uninitialized; then filling in properties the type of this will narrow until all properties are specified. Adapting the example from above:

type su = string | undefined;
constructor(x: string) {
    // this: {a1: su, a2: su, a3: su, a4: su, a5: su, a6: su, a100: su}
    super();
    // this: {a1: su, a2: su, a3: su, a4: su, a5: su, a6: su, a100: su}
    this.a1 = this.getSomething(x);
    // this: {a1: string, a2: su, a3: su, a4: su, a5: su, a6: su, a100: su}
    this.a2 = 'a2'
    // this: {a1: string, a2: string, a3: su, a4: su, a5: su, a6: su, a100: su}
    this.a3 = indirectOK(this);
    // this: {a1: string, a2: string, a3: string, a4: su, a5: su, a6: su, a100: su}
    this.a4 = 'a4'
    // this: {a1: string, a2: string, a3: string, a4: string, a5: su, a6: su, a100: su}
    this.a5 = this.myGetter;
    // this: {a1: string, a2: string, a3: string, a4: string, a5: string, a6: su, a100: su}
    this.a6 = 'a6';
    // this: {a1: string, a2: string, a3: string, a4: string, a5: string, a6: string, a100: su}
    this.a100 = 'a100';
    // this: MaybeInitializedCorrectly
}

This progressive narrowing is conceptually similar to starting out with Partial<this> and then applying successive type guards of the form if (this.attr !== undefined). Methods that handle initialization would add a this annotation to indicate that they accept partially-initialized instances. E.g., in this example the calls to indirectOK and indirectNotOK fail since the type of this is not yet MaybeInitializedCorrectly. Similarly calling .getSomething and .getAnotherThing fails, unless they explicitly accepted partially-initialized instances:

getSomething(this: {}, a: string) {...}
getAnotherThing(this: {a100: string}) {...}

this.a2 = this.getAnotherThing(); would be a compile-time error.

Side note: I think the compiler would have to reject the overriding of getAnotherThing since {a100: string} is not compatible with SomeBase, but this may end up being a limitation of the unsound nature of TS. Haven’t thought through the sub/super type implications here

Tuesday constructors

Factored out pseudo-constructors like initTuesday need two tell the compiler two things: (1) Which fields must be initialized prior to calling them? (2) Which fields are initialized and to what types? (1) is the same as the getSomething problem for vanilla types, so the same this annotation would fix this. (2) Is entirely different, and I’m not sure what the best approach for this is. Here are a couple of ideas:

  • The compiler could statically determine/guess which properties are initialized
  • Add syntax similar to type guards: initTuesday(this: {}) this sets {a1: string}
  • Overload the type guard syntax: initTuesday(this: {}) this is {a1: string}

Deferred constructors

This is a bit of an orthogonal issue, since everything else was confined to the constructor’s dynamic scope, but let me throw out an idea here. new PooledObject() has type PartiallyInitialized<PooledObject>, in which all fields are optional. The realInit method uses the same syntax as Tuesday constructors to indicate that after calling it the object is fully initialized. It then acts sort of like a type-guard wherever it is called. Functions/methods that operate on uninitialized objects opt in to accepting them by using the PartiallyInitialized<PooledObject> type. Calling other functions with an uninitialized object is a compiler error.

class PooledObject extends DeferredInitializer<Object> {
    attr: string;
    constructor() { console.log("empty constructor"); }
    realInit(this: {}, x: string) { this.attr = x; }
}

const p = new PooledObject(); 
// p has type PartiallyInitialized<PooledObject> | PooledObject
p.realInit('attr');
// p has type PooledObject.

Obviously, all of this behavior should be behind a flag for backwards comp. There’s of course still the question of what should the compiler do when it detects incomplete initialization [i.e., some non-optional properties are possibly still undefined] after a constructor returns? My preference would be to fail with a compiler error (addressing @MicahZoltu’s concerns) unless the class has somehow indicated to the compiler that is uses the pool-style initialization, by extending from a certain generic class, using a decorator, or some sort of syntax extension.

I also would like to say that I appreciate the predicament the TypeScript team is in. You have people like me and others in this issue that really want strict rules, lots of safety, etc. but at the same time in order for TypeScript to thrive you need to attract JavaScript developers into the ecosystem, and ease them into a “better” way of doing things. The only reason I piped up in this issue originally was because I ran into the problem, found the issue, and didn’t see a clear resolution, just a couple of short comments then closed. I really do appreciate you coming and giving us additional details about the process, the problems and the ideas you are toying with. As a developer I recognize that this perhaps community development perhaps isn’t at the top of your “things I want to do with my day” list but please know that it is appreciated, even when I don’t like the final decision. Ultimately, I will choose TypeScript as long as it is the best option even if I disagree with some decisions and at the moment, TypeScript is a strong contender for best options.

My vote would be a single opt-in option that provides the most guarantees the language can provide, and then relax the constraints over time as the type analyzer gets better/smarter. If this means that I cannot reference this at all until after I have assigned all properties, that is a price I will happily pay, even if I do grumble a bit under my breath when it hits me. If I really want, I can do a temporary assignment to the property (to satisfy the checker) then do the calls I need to populate the property correctly. This ugliness would be constrained to the constructor and could be documented with comments for clarity with warnings to future developers. If my constructor gets so big that this is a real issue, then to me that is a code smell that my class is too complex and perhaps should be broken up into simpler classes.

Later, if the static analyzer gets smart enough to do some more advanced analysis, these rules could be loosened.

I recognize that I am on the opposite end of the spectrum from a lot of JavaScript developers. I really want to be writing Ada or something with full design by contract support and extremely rigorous type safety, but I’m forced to develop for the browser and TypeScript is the best tool for the job at the moment as it gives me the most static constraints, so my vote will always be for more constraints that improve safety.

We try really hard to make it so that you’re not writing code differently (in terms of actual runtime code) from how you would if the typechecker didn’t exist.

I don’t think it is wise to hold back the type checker (and the language itself) just to adhere to old javascript patterns. More bugs will be caught by the compilation process which is a net gain for the whole TS community. Typescript is not a dynamic language like javascript, please let it grow to its full potential.

I disagree with the initial proposed solution, but I don’t understand why this is closed as Won’t Fix, since this is clearly unintuitive behavior currently.

class Greeting {
  person;
  sayHi () {
    alert('Hi ' + this.person.firstName);
  }
}

let greeting = new Greeting();
greeting.sayHi();

In this example, there’s no possible way person could have been defined. TypeScript already generates “Object is possibly null” errors. Why can’t TypeScript simply generate the same “Object is possibly null” error here? I’ll see the error and update my code to if (this.person) { ... }

Note: I disagree with the proposal to require properties to be initialized in the constructor. It is extremely common not to initialize a value until receiving the response for an async http request, either in the constructor, or later. There is no issue with allowing the property to remain uninitialized indefinitely. The issue only arises when trying to access a property that has not yet been initialized, which seems to be precisely why the “Object is possibly null” error exists.

I made a toy implementation of this feature. I am not a compiler engineer nor familiar with the typescript source, but it kinda works. No support for abstract/base classes at the moment. helmutschneider/TypeScript@af9e5fbae no-uninitialized

edit: rule for tslint

@ahejlsberg thank you for keeping an open mind. all the work of the team over some big milestones has been greatly appreciated.

@cevek @gcanti this is unrelated to issue #8476 - I suggest moving to a separate thread, or @cevek filing a new issue regarding it. Would be best to keep the discussion here about property initialization in constructors.

@RyanCavanaugh

I want

I want following rules.

  1. super() can not be called before complete field initialization
  2. instance method can not be called before complete field initialization
  3. this can not be passing for function before complete field initialization
  4. this.field1 can not be get before set.

Question answers

My answers for your questions are below.

Are you allowed to call methods on this before all properties have been initialized (a1, a2 below)?

No. By rule 2.

Are you allowed to reference this in a parameter position when calling some global (a3, a4)?

No. By rule 3.

Are you allowed to reference getters of your own class before everything is initialized (a5)?

No. By rule 2. Because accessing getter is same as calling instance method. So compiler need to distinguish pure field and getter.

Are you allowed to call a global function (or method of some other free variable) (a6)?

Yes. But problem is solved by rule 1. By rule 1, when SomeBase.constructor is called, all fields in MaybeInitializedCorrectly are initialized.

Description with code

With my specs, compiler see code as below.

	constructor(x: string) {
		// rejected by rule 1
		super();
		
		// rejected by rule 2
		this.a1 = this.getSomething(x);
		
		// rejected by rule 2
		this.a2 = this.getAnotherThing();

		// rejected by rule 3
		this.a3 = indirectOK(this);
		// rejected by rule 3
		this.a4 = indirectNotOK(this);

		// rejected by rule 2
		this.a5 = this.myGetter;

		// ok
		this.a6 = someGlobal();
	}

Maybe this is more about philosophy than code quality and developer preference. Let’s look at the facts:

Many real customers are coming here, seeing the issue and supporting the change. How many have come to see the issue and been opposed? The numbers don’t add up. Unless I’m missing some data you’re deciding this by fiat rather than by data or customer wishes.

You argue that “what has been successful in a greenfield language is not germaine”. Seriously? How many language designers do you know who would defend that statement unqualified? On the contrary every lecture or paper I see explicitly talks about decisions made based on work and results in other languages. Compatibility addressed below.

How is backward compatibility not a red herring considering a flag would render it moot? I don’t recall anyone insisting the defaults must be changed. Remember javascript strict?

Only after all this feedback, and after other arguments do not seem well supported, we just now are offered example scenarios that may be problematic. Is this a serious request for review such that if we can help resolve the proffered scenario, then there will be a green light? Or then would the goal post move again?

>nearly everyone would expect this code to check without error

@RyanCavanaugh this seems to be the point of misunderstanding, may I ask how you came to that conclusion?

The response your getting may be deceiving if you are asking devs who have not lived with the behavior. When I started Swift projects many of on on the team did not like it either. The catch is that we quickly grew accustomed and started to prefer the behavior after some ramp up. So how do you know you’re not hearing the same thing we did?

I’m trying to consider all perspectives, I was in product dev at MS for a number of years, I get the Visual Studio culture/mindset. Point is I don’t think this is a fringe perspective.

Swift’s approach to solve both strictness and lazy initialization is to mark such properties explicitly. Swift is really strict about nil but you can turn off this check if you know non-null property is initialized after init() and before used. ! means “implicitly unwrapped optional” type.

class Foobar {
  let foo: Int // Compiler error!
  var bar: Int! // This is not an error.
}

I think in TypeScript it can be represented by introducing special keyword.

class Foobar {
  foo: T // Compiler error!
  bar: T | lazy // No error. Strict check is turned off.
  lazy baz: T // Or this might be better.
}

(NOTE: Swift’s lazy keyword behaves quite different than this.)

This comes off as really rude when we’ve been engaging in good faith here

You find it rude that I’m attempting to understand the decision process and am drawing conclusions based of the information that has been available? There has been no ad hominem here.

[some talk about flowtype and eslint]

That doesn’t sound like arguing the merits, it sounds like you are saying if we don’t like it then don’t use it. We do like it. That’s why we’re here, advocating based on the facts available.

If the only outcome you’re willing to accept as legitimate is the one you agree with, then I cannot guarantee you satisfaction.

It’s bizarre that you would draw that conclusion. I don’t recall saying or suggesting anything of the sort. On the contrary, I consider anything I’ve ever said to be falsifiable. More over I don’t view you as having any obligation to do what I think is right. My only interest is for the best decision to prevail which in computer science, often involves debate and disagreement.

Does it really seem so unusual that an issue that spurred 5 people you work with to yell at each other for hours, drew a few pointed questions on a public forum? If you think I’m so rude was all that yelling so much more polite?

It’s not my intention to make you feel attacked. I have a lot of respect for what you all have built as the leading tool in its class. We’ve chosen to follow you instead of flowtype because it’s deserved. It’s just debate, believe it or not without ego or agenda.

We try really hard to make it so that you’re not writing code differently (in terms of actual runtime code) from how you would if the typechecker didn’t exist. If calling a setup method was how you wanted to write your constructor, you should be able to do that without a lot of pain. If you know your setup method initialized prop, you shouldn’t have to ! or if(this.prop) { away its undefinedness in all other methods. If you don’t need to put a bunch of dummy initializations in the constructor, you shouldn’t have to.

I think comparisons to a greenfield programming language aren’t particularly germane. It’s a different situation to have an initialization rule out of the gate, compared to applying a new rule to existing code.

after all properties that can not be initialized directly in the constructor can still be marked [with ?]

The sheer badness of this is why we’re really hesitant on this rule. If you do this, then:

  • The class is not assignable to something demanding prop: T, even though the whole idea is that prop should not observably be undefined after the constructor
  • You can assign undefined to prop anywhere in your code, even though the whole idea is that you don’t want prop to be undefined
  • You have to test for undefined or ! it away in all your class methods, even though you intend to initialize it (indirectly) in the constructor and have it never be undefined again

OK, as pointed out by someone in another thread, this is one of the non-goals of TypeScript.


I’d suggest that, under some flags, instead of warning user of type safety caused by the default undefined value, give it a default value like C#, if the default value can’t be determined, emit an error.

// under a flag like `--auto_init_properties`
class Person {
  id: number; // auto initialized to 0
  name: string; // auto initialized to ""
  description: string|null; // auto initialized to null

  // **Compilation error: Can't determine default value for this prop, all properties must be initialized. **
  opt: string|number|Option; 

  // To fix this, user must specify an value, for example:
  opt: string|number|Option = 'abc';
  opt: string|number|Option = new Option();
}

In this way, all properties are actually defined in runtime:

const obj = new Person();
for (const property in obj) {
  console.log(property);
}

// Output: id, name, description, opt

Also, we don’t need to write boilerplate code to enforce strict type safety:

class Person {
  constructor {
    this.id = 0;
    this.name = '';
    this.description = null;
    this.opt = new Option();
  }
}

Started a new issues at #19750

I might be thick headed here, but why not just making sure you cannot declare a property without initializing it: This would give an error private myString:string

You would have to do private myString:string = ""

That would solve all things with a simple check, only that people would have to do an extra initialization, or this could even be done in the compilation so that in runtime myString will never be undefined.

I don’t understand why a flag for this feature would be a “half-baked solution”; what’s actually half-baked is the current limited behavior of --strictNullChecks.

I’d also want to point out that web dev is not the only thing where TypeScript is used. The other use cases for TypeScript is indie game dev that allows to write one game and deploy it on mobile platforms, html5 platform, desktop platform etc.

When someone uses TypeScript for gamdev then performance is very, very important thing. Forcing to initialize all fields in constructor gives a huge performance benefit in V8 there engine can assign object immediately to hidden class. If you however miss to initialize one property in constructor, V8 will reasign hidden class to another one when you first assign missing property, causing performance drop. If this object is commonly used object in game loop every 1/60 s frame, you just created huge performance hog.

Discussed some more. One open question is where to draw the line on what’s considered an acceptable amount of initialization. It’s impossible to be “airtight” and there would need to be some cutoff that balances usability vs soundness.

The simplest rule is that all properties must have at least one assignment in the constructor before any return point, presumably with CFA to assert that you don’t read from properties before they’re definitely assigned. This catches basic “I forgot” errors, but won’t catch all possible observations of properties of the class in an undefined state.

Are you allowed to call methods on this before all properties have been initialized (a1, a2 below)? On the one hand, the methods may observe uninitialized properties. On the other hand, you will often want to defer to some class logic to get an initial value for a property.

Are you allowed to reference this in a parameter position when calling some global (a3, a4)? Again, useful, but those globals might observe undefined properties. This gets hazier – what if the declared type of the parameter doesn’t include any declarations for your not-yet-initialized properties? Maybe safe, except that if it does refer to your instance methods, then those instance methods might read uninitialized state.

Are you allowed to reference getters of your own class before everything is initialized (a5)? Some getters will just return constants and there’s no problem. But what if a derived class overrides the getter and tries to read an uninitialized property?

Are you allowed to call a global function (or method of some other free variable) (a6)? Seems safe, but your base class might have stored a reference to this somewhere, and that function may in turn reference what turn out to be your uninitialized properties!

And even the setting itself is problematic! A derived class can define a setter for a property and reference arbitrary other properties in that setter. So we cannot possibly prove that you never observe an uninitialized property.

So the line has to be drawn somewhere and I’m not entirely sure where.

class SomeBase {
	constructor() {
		lastInstance = this;
	}
	getAnotherThing() {
		return 'hmmm';
	}
}

class MaybeInitializedCorrectly extends SomeBase {
	a1: string;
	a2: string;
	a3: string;
	a4: string;
	a5: string;
	a6: string;
	a100: string;
	constructor(x: string) {
		super();
		// Calls to this.methods() - OK or no?
		// A not-problematic call
		this.a1 = this.getSomething(x);
		// A problematic call
		this.a2 = this.getAnotherThing();

		// Calls involving 'this' - OK or no?
		// Fine
		this.a3 = indirectOK(this);
		// Bad
		this.a4 = indirectNotOK(this);

		// Getters? See comment in getter body
		this.a5 = this.myGetter;

		// Global function calls?
		// This looks fine but actually reads the a100 property!
		this.a6 = someGlobal();
	}

	get myGetter() {
		// But what if a derived class overrides with
		// return this.a100; ?
		return 'Always fine';
	}

	// Note that getSomething might be overriden by a derived class,
	// so CFA of its body is not a comprehensive solution
	getSomething(a: string) {
		return a.toLowerCase();
	}
	getAnotherThing() {
		return this.a100;
	}
}

// Again, function bodies may not be visible, so CFA is not useful
function indirectOK(x: MaybeInitializedCorrectly) {
	return 'whatevs';
}
function indirectNotOK(x: MaybeInitializedCorrectly) {
	return x.a100;
}

var lastInstance: SomeBase; 
function someGlobal() {
	return lastInstance.getAnotherThing(); 
}

why not just make it a flag with a bitmask specifying which of the things I’m allowed to do

i concur with the other commenters. properties that are not initialized should be defined as:

private prop: T | undefined

if checking initialization in all constructor code paths is to complicated, than all properties should need to have undefined type added with strictNullChecks.

the short notation proposed by @ethanresnick also makes much sense:

public prop?: T; // ? means this is T | undefined

as other commenters have pointed out. swift handles this in a very similar way.

it is really annoying to have to debug effects by properties being undefined at runtime, but it is quite easy to forget to initialize a property.

ummm…

class NonAbstractClass { // <-- Non-abstract class, so can be instantiated by itself
    readonly imNotInitialized: number; // <-- can only be initialized here

    constructor() {
        // <-- or here
    }
}
class Base {
    readonly imNotInitializedToTheRightType: any;

    constructor() {
        this.imNotInitializedToTheRightType = "hi";
    }
}

class Derived extends Base {
    readonly imNotInitializedToTheRightType: number; // <-- is this initialized?

    constructor() {
        super();
    }
}

@Ristaaf The type might be something you can’t create directly, but can only obtain from the runtime environment or a third-party library, e.g.:

// Creating an instance of this class requires
// you first open a connection to a database
class Backend {
    connection: DatabaseConnection;
    constructor(db: DatabaseConnection) {
        this.connection = db;
    }
}

Another example would be dependency injection in Angular - you wouldn’t want to have to create an instance of each of the dependencies you use (which may incur non-trivial initialization costs) just to have the constructor immediately overwrite them with the injected versions.

Kotlin, the language similar to Swift (or Java’s counterpart to Swift) which runs on JVM, has lateinit keyword for this. https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties

@cevek weird, I’m on 2.2.2 and I can see the error in VS Code ts1

EDIT: sorry for the off-topic

on the other hand there also might be a code path (for example a async one) that leaves the property undefined. in this case all the above points make sense. at least by testing for undefined or using ! the developer is aware of them.

with swift this is the same. optional properties (similar to adding type undefined) have the same constraints. to mitigate swift introduces a syntax which would convert to:

public prop!: T; // meaning T or undefined, but assume not undefined

this way further code does not have to check for undefined or use !. the property type still shows that type safety can not be guaranteed.

i am not sure if typescript already has something similar. it might help in this case.

@ahejlsberg Can you give details on the research you did and the problems this would cause? What is the process for getting this re-evaluated?

The design meeting notes found at https://github.com/Microsoft/TypeScript/issues/8503 sound like thought was put into this issue but my takeaway from them is that this is still a worthwhile feature, even if it is hard or has to be behind a flag. As seen in this issue and the many duplicate issues there are many users that are expecting this behavior and surprised when it doesn’t happen with strictNullCheck. The volume of feedback and thumbs ups this issue has received is fairly substantial. As a TypeScript advocate, I have been touting how TS 2.0 added null checking and then when I go to actually use it I find out it basically doesn’t work, it is a very frustrating experience.

Re the argument that enforcing this would be too much of an irritant… maybe I’m missing something, but isn’t the “irritant” just adding one character (a ?) after each property name?

I.e.:

class X {
  public member: string;
  public memberLateInitialized?: string; // ? means this is string | undefined
}

If that’s it, then it definitely seems worth it imo

@DavidSouther agree, thanks for the suggestion.

Yeah, probably something like --allowUninitializedProperties, but since it only happens under the new --strictNullChecks I suppose we could wait on adding the flag and gauge the feedback.