Skip to content

Check non-undefined properties are initialized in the constructor with --strictNullChecks #8476

Description

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

Activity

  1. ahejlsberg commented on May 5, 2016

    @ahejlsberg
    Member

    Since we're not able to track assignment effects between methods, we'd break code that calls a separate initialize method (or some such) to initialize instance data. I'm not a fan of that pattern, but I have certainly run across it.

    Also, presumably we want to permit uninitialized properties in abstract classes. That means we'd have to check not just locally declared properties but also inherited properties in non-abstract classes.

  2. mhegazy commented on May 5, 2016

    @mhegazy
    ContributorAuthor

    under a flag then?

  3. ahejlsberg commented on May 5, 2016

    @ahejlsberg
    Member

    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.

  4. ahejlsberg commented on May 11, 2016

    @ahejlsberg
    Member

    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.

  5. added
    Won't FixThe severity and priority of this issue do not warrant the time or complexity needed to fix it
    SuggestionAn idea for TypeScript
    and removed
    BugA bug in TypeScript
    on May 11, 2016
  6. malibuzios commented on May 12, 2016

    @malibuzios

    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();
        }
    }
  7. malibuzios commented on May 14, 2016

    @malibuzios

    Anders Hejlsberg (@ahejlsberg)

    I think I've shown one common scenario where this would be a help, not an irritant: namelyreadonly members in a non-abstract class, and one less common, but probably more 'severe' one: inherited members initialized to incompatible or less capable types than the type they are redeclared to in a derived class.

    I haven't yet got any response from you on this. Why would you think it would be an irritant here?

    (also, I haven't got any responses for the questions about inheritance of readonly members by writable members and vice-versa. It has been a week so far, and neither you or anyone of the team have shown serious intentions to engage in that discussion. I can't really see a reason why not - if your original reasoning was solid it should be relatively easy to explain..)

  8. ahejlsberg commented on May 14, 2016

    @ahejlsberg
    Member

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

  9. peterkelly commented on Jun 5, 2016

    @peterkelly

    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";
            }
        }
    }
    
  10. 74 remaining items

  11. raveclassic commented on Oct 9, 2017

    @raveclassic

    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?

  12. mhegazy commented on Oct 9, 2017

    @mhegazy
    ContributorAuthor

    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?

    it is on our list of features to support. we do not have an ETA for it at the time being.

  13. mgenware commented on Nov 5, 2017

    @mgenware

    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

  14. ahejlsberg commented on Nov 16, 2017

    @ahejlsberg
    Member

    This suggestion is now implemented by #20075.

  15. whitneyland commented on Nov 17, 2017

    @whitneyland

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

  16. peterkelly commented on Nov 17, 2017

    @peterkelly

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

  17. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions