Skip to content

Re-assigning to a variable with a recursive type got unexpected type narrowing result. #38629

@ir193

Description

@ir193

TypeScript Version: 3.9.2

Search Terms: Control Flow, Type narrowing

Expected behavior: 👍 Compile

Actual behavior: Property 'next' does not exist on type '{ type: "zero"; }'.(2339)

Related Issues: 28763

Code

type SomeType = { type : 'zero' }
              | { type : 'one', hello : string, next : SomeType };

let mutable : SomeType = { type : 'zero' };

for(let i=0; i<2; i++){
    console.log(mutable);
    if( i==0 ){
        mutable = { type : 'one', hello : "hello", next : mutable };
    } else {
        switch( mutable.type ){
            case 'zero' :
                break;
            case 'one' :
                let tmp : SomeType = mutable.next;  // this is ok 
                let s : string = mutable.hello;     // this is ok
                
                mutable = mutable.next;             // this got Property 'next' does not exist on type '{ type: "zero"; }'.
                break;
        }
    }
}

In the case 'one' branch, mutable should be narrowed to { type : 'one', hello : string, next : SomeType }. Accessing fields including next is OK. But when assign it back to variable mutable back, Ian error. It seems inferring mutable has the type { type : 'zero' } instead.

Output
"use strict";
let mutable = { type: 'zero' };
for (let i = 0; i < 2; i++) {
    console.log(mutable);
    if (i == 0) {
        mutable = { type: 'one', hello: "hello", next: mutable };
    }
    else {
        switch (mutable.type) {
            case 'zero':
                break;
            case 'one':
                let tmp = mutable.next; // this is ok 
                let s = mutable.hello; // this is ok
                mutable = mutable.next; // this got Property 'next' does not exist on type '{ type: "zero"; }'.
                break;
        }
    }
}
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": 2,
    "target": "ES2017",
    "jsx": "React",
    "module": "ESNext"
  }
}

Playground Link: Provided

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions