Skip to content

Optional chaining breaks inference of conditional types #35655

Closed
@goatfryed

Description

@goatfryed

TypeScript Version: 3.7.2

Search Terms: Optional Chaining, Generic type, type information lost

While playing around on my side project, I found an edge case where optional chaining would fail to infer the correct types, while everything works fine without it.

Expected behavior: Line 30 yields no type error. Optional chaining narrows generic parameter down correctly and infers type {hello: "there", general: "kenobi} as in Line 23.

Actual behavior: TS yields error on line 29 and 30, data not in type 'A'. it seems like any type information of the generic type is lost.

Related Issues: #35274 #32735

Code

interface Foo { }
interface Bar { }
interface FooBar<A> { data: A}

type FooBarFactory<A> = A extends FooBar<infer T> ? {
  apply(foo: Foo, arg: T): BarFactory<A> | undefined
} : never;

interface BarFactory<A> {
  apply(bar: Bar): A,
}

type DomainFooBar = FooBar<{ hello: "there", general: "kenobi" }> | FooBar<{chuck: "norris"}>

function test(declaration: FooBarFactory<DomainFooBar>) {
  const f1 = declaration.apply({},{hello: "there", general: "kenobi", chuck: "norris"});
  if (!f1) return;
  const f2 = f1.apply({});
  if (!f2) return;

  if ("hello" in f2.data) {
      f2.data.general
  }

  const fooBar = declaration.apply({},{hello: "there", general: "kenobi", chuck: "norris"})?.apply({});
  if (!fooBar) return;

  if ("hello" in fooBar.data) {
      fooBar.data.general
  }
}
Output
"use strict";
function test(declaration) {
    var _a;
    const f1 = declaration.apply({}, { hello: "there", general: "kenobi", chuck: "norris" });
    if (!f1)
        return;
    const f2 = f1.apply({});
    if (!f2)
        return;
    if ("hello" in f2.data) {
        f2.data.general;
    }
    const fooBar = (_a = declaration.apply({}, { hello: "there", general: "kenobi", chuck: "norris" })) === null || _a === void 0 ? void 0 : _a.apply({});
    if (!fooBar)
        return;
    if ("hello" in fooBar.data) {
        fooBar.data.general;
    }
}
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "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