Skip to content

[regression 4.8] type narrowed too far; valid cases considered unreachable #50527

Closed
@seansfkelley

Description

@seansfkelley

Bug Report

🔎 Search Terms

type narrowing assertNever never unreachable

🕗 Version & Regression Information

  • This changed between versions 4.7.4 and 4.8.x

⏯ Playground Link

Playground Link: Provided

💻 Code

enum PropertyType {
  String = 'string',
  Number = 'number',
  Unknown = 'unknown',
}

interface Property<Type extends PropertyType, Value extends unknown = unknown> {
  type: Type;
  value: Value;
}

type StringProperty = Property<PropertyType.String, string>;
type NumberProperty = Property<PropertyType.Number, number>;
type UnknownProperty = Property<PropertyType.Unknown, unknown>;

type PropertyObject =
  | StringProperty
  | NumberProperty
  | UnknownProperty

type PropertyObjectOrEmpty = PropertyObject | { type: PropertyType; value: undefined };

interface Edit<VT extends PropertyObject = PropertyObject> {
  type: 'edit';
  property: VT;
}

interface Add<VT extends PropertyObjectOrEmpty = PropertyObjectOrEmpty> {
  type: 'add';
  property: VT;
}

function assertNever(n: never): never {
  throw new Error('nope');
}

function doStuff(action: Edit | Add) {
  if (action.property.value == null) {
    // do something
  } else if (action.property.type === PropertyType.Unknown) {
    // do something else
  } else {
    // this line is reachable, but compiles without error!
    assertNever(action.property);
  }
}

doStuff({
  type: 'edit',
  property: {
    type: PropertyType.String,
    value: 'hello, world'
  }
})
Output
"use strict";
var PropertyType;
(function (PropertyType) {
    PropertyType["String"] = "string";
    PropertyType["Number"] = "number";
    PropertyType["Unknown"] = "unknown";
})(PropertyType || (PropertyType = {}));
function assertNever(n) {
    throw new Error('nope');
}
function doStuff(action) {
    if (action.property.value == null) {
        // do something
    }
    else if (action.property.type === PropertyType.Unknown) {
        // do something else
    }
    else {
        // this line is reachable, but compiles without error!
        assertNever(action.property);
    }
}
doStuff({
    type: 'edit',
    property: {
        type: PropertyType.String,
        value: 'hello, world'
    }
});
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

🙁 Actual behavior

Code compiles without error, but assertNever is hit at runtime.

🙂 Expected behavior

Compilation error: the type of action.property at the point it's passed to assertNever is not never. (4.7 does this.)

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issueHas ReproThis issue has compiler-backed repros: https://aka.ms/ts-repros

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions