Skip to content

strictNullChecks mode - variable initial value is ignored in the control flow #10640

Closed

Description

@aliai

TypeScript Version: nightly (2.0.2)

In strictNullChecks mode only:

Code

    interface IFoo {
      foo?: string;
    };

    var a: IFoo = { foo: '' };
    a.foo.split('');  // <-- Error: a.foo is possibly undefined!

Expected behavior:

This is a valid piece of code. a.foo is defined, therefore, a.foo.split is a valid expression.

Actual behavior:

a.foo is concluded as type string | defined, even though it is defined in the initiation phase var a:IFoo = { foo: '' }. tslint/typescript complains that foo is possibly undefined and therefore, cannot access to split method.

the following code runs without problem simply because foo is assigned after a declaration:

    var a: IFoo = {};
    a.foo: '';
    a.foo.split('');

Activity

  1. alitaheri commented on Aug 31, 2016

    @alitaheri

    { foo: '' } is assignable to IFoo. so the type of a becomes IFoo. in the second code you're assigning something to a.foo and the control flow analyzer can prove that a.foo has value. I understand what you're asking the type-checker is valid, but it's way too far fetched!

    Embrace for By Design or Working as Intended 😅

  2. aliai commented on Aug 31, 2016

    @aliai
    Author

    I don't know if I've managed to explain the problem clearly. I'll elaborate :)

    The first code snippet doesn't work and that's the bug I am writing about. a.foo is initialized as a string and not a string|undefined. The linter fails to recognize that there is a initial value for a. And therefore, I cannot call split method on a.foo because it thinks that it is string|undefined.

    The code fails on the last line a.foo.split('')

  3. HerringtonDarkholme commented on Aug 31, 2016

    @HerringtonDarkholme
    Contributor

    Ali Taheri Moghaddar (@alitaheri) This snippet might help illustrate the problem.

    var a: string | null = 'str'
    
    a.split('123') // no error
    
    a = null
    
    a.split('123') // error
  4. alitaheri commented on Aug 31, 2016

    @alitaheri

    Ali Arafati (@aliai) Herrington Darkholme (@HerringtonDarkholme) I did get the problem. It's just that I thought maybe the cast to IFoo erases the information about foo.

    Interestingly, this code doesn't work either:

    interface IFoo {
      foo?: string;
    };
    
    let a: IFoo;
    a = { foo: '' };
    a.foo.split('');  // <-- Error: a.foo is possibly undefined!

    This sure does look like a bug to me 😅

  5. danielearwicker commented on Sep 2, 2016

    @danielearwicker

    Naturally this works fine (inferred type of a):

    let a = { foo: '' };
    a.foo.split('');  // <-- no error
    

    The reason for adding the explicit type annotation is because you are planning to assign to a objects that may or may not have a foo property. Therefore code such as a.foo.split does need to be protected against that possibility. In future maintenance it might move away from its current position and get a line inserted before it that changes a. The compiler is being helpful based on what you've told it.

    If you don't want the help, don't ask for it! :) (just remove the type annotation from a).

  6. aliai commented on Sep 2, 2016

    @aliai
    Author

    Daniel Earwicker (@danielearwicker) yes for the example I provided it may look silly to annotate the type :) But, this is just a simplified example to show the bug, not the necessity. The use case of this can vary of course:

        interface IUser {
          name: string;
          permissions?: {
            adminRights?: {
                moreInDepthFileld: any;
                thatIWouldLikeToHaveIntellisenseFor: any;
            };
            canPublishPost: boolean;
            canLeaveComments: boolean;
          };
          otherThingsIMightWantForMyObject?: ISomething;
        };
    
        var user: IUser = { name: 'foo', permissions: {} };
        user.permissions.canPublishPost = ...; // <-- Error

    This is not the code I encountered this problem with since it is out of topic here. But it is a close example to the real case. The point being, the compiler doesn't take the initialization phase of the variable into account to not infer object|undefined type in strictNullCheck mode.

  7. mhegazy commented on Sep 21, 2016

    @mhegazy
    Contributor

    We only narrow unions. the type of a is not, it is an IFoo. so a is not narrowed. so perhaps we need to consider property declarations as if they were assignments, i.e. treat it as:

    var a: IFoo = {};
    a.foo = "";
    ``
  8. added
    RevisitAn issue worth coming back to
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    and removed on Sep 28, 2016
  9. RyanCavanaugh commented on Sep 28, 2016

    @RyanCavanaugh
    Member

    There are some technical challenges here that would make this rather expensive in our current design. We'll think about it more and see if there's something that can be done, but there isn't an obvious solution at this time.

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

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixedRevisitAn issue worth coming back toSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions