Skip to content

Optional chaining breaks inference of conditional types #35655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
goatfryed opened this issue Dec 12, 2019 · 1 comment · Fixed by #35949
Closed

Optional chaining breaks inference of conditional types #35655

goatfryed opened this issue Dec 12, 2019 · 1 comment · Fixed by #35949
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@goatfryed
Copy link

goatfryed commented Dec 12, 2019

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

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Dec 20, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.8.1 milestone Dec 20, 2019
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Dec 20, 2019

Removed some extraneous code:

interface Box<T> { data: T }
type BoxTypes = Box<{ x: string }> | Box<{ y: string }>;

type BoxFactoryFactory<TBox> = TBox extends Box<infer T> ? {
  (arg: T): BoxFactory<TBox> | undefined
} : never;

interface BoxFactory<A> {
  getBox(): A,
}

declare const f: BoxFactoryFactory<BoxTypes>;
const b = f({ x: "", y: "" })?.getBox();
if (b) {
  // Generic type parameter leak
  const x = b.data;
}

@ahejlsberg ahejlsberg added the Fix Available A PR has been opened for this issue label Jan 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants