Skip to content

A behavior of conditional types is changed (regression) #30047

Open

Description

Ryan Cavanaugh (@RyanCavanaugh) Wesley Wigham (@weswigham) Anders Hejlsberg (@ahejlsberg) Caused by #27697

TypeScript Version: 3.4.0-dev.20190222

Search Terms:

Code

type Attrs = Record<string, string | EventListener | null | undefined>;
type Children =
  | Children.Void
  | Children.Text
  | Children.Collection
  | Children.Record;
namespace Children {
  export type Void = undefined;
  export type Text = string;
  export interface Collection extends ReadonlyArray<El> { }
  export type Record = { [field: string]: El; };
}
interface El<
  T extends string = string,
  E extends Element = Element,
  C extends Children = Children,
  > {
  readonly tag?: T;
  readonly element: E;
  children: Relax<C>;
}
type Relax<C extends Children> = C extends Children.Text ? Children.Text : C;

declare function f<C extends Children>(              children: C): C;
declare function f                    (attrs: Attrs,            ): void;
declare function f<C extends Children>(attrs: Attrs, children: C): C;
f({ a: 0 as any as El<'a', HTMLAnchorElement> }); // ok
f({ a: 0 as any as El<'a', HTMLAnchorElement, Children> }); // ok
f({ a: 0 as any as El<'a', HTMLAnchorElement, Children.Void> }); // error since 3.4.0-dev.20190222
f({ a: 0 as any as El<'a', HTMLAnchorElement, Children.Text> }); // error since 3.4.0-dev.20190222

Expected behavior:

pass

Actual behavior:

error

Playground Link: http://www.typescriptlang.org/play/index.html#src=type%20Attrs%20%3D%20Record%3Cstring%2C%20string%20%7C%20EventListener%20%7C%20null%20%7C%20undefined%3E%3B%0D%0Atype%20Children%20%3D%0D%0A%20%20%7C%20Children.Void%0D%0A%20%20%7C%20Children.Text%0D%0A%20%20%7C%20Children.Collection%0D%0A%20%20%7C%20Children.Record%3B%0D%0Anamespace%20Children%20%7B%0D%0A%20%20export%20type%20Void%20%3D%20undefined%3B%0D%0A%20%20export%20type%20Text%20%3D%20string%3B%0D%0A%20%20export%20interface%20Collection%20extends%20ReadonlyArray%3CEl%3E%20%7B%20%7D%0D%0A%20%20export%20type%20Record%20%3D%20%7B%20%5Bfield%3A%20string%5D%3A%20El%3B%20%7D%3B%0D%0A%7D%0D%0Ainterface%20El%3C%0D%0A%20%20T%20extends%20string%20%3D%20string%2C%0D%0A%20%20E%20extends%20Element%20%3D%20Element%2C%0D%0A%20%20C%20extends%20Children%20%3D%20Children%2C%0D%0A%20%20%3E%20%7B%0D%0A%20%20readonly%20tag%3F%3A%20T%3B%0D%0A%20%20readonly%20element%3A%20E%3B%0D%0A%20%20children%3A%20Relax%3CC%3E%3B%0D%0A%7D%0D%0Atype%20Relax%3CC%20extends%20Children%3E%20%3D%20C%20extends%20Children.Text%20%3F%20Children.Text%20%3A%20C%3B%0D%0A%0D%0Adeclare%20function%20f%3CC%20extends%20Children%3E(%20%20%20%20%20%20%20%20%20%20%20%20%20%20children%3A%20C)%3A%20C%3B%0D%0Adeclare%20function%20f%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(attrs%3A%20Attrs%2C%20%20%20%20%20%20%20%20%20%20%20%20)%3A%20void%3B%0D%0Adeclare%20function%20f%3CC%20extends%20Children%3E(attrs%3A%20Attrs%2C%20children%3A%20C)%3A%20C%3B%0D%0Af(%7B%20a%3A%200%20as%20any%20as%20El%3C'a'%2C%20HTMLAnchorElement%3E%20%7D)%3B%0D%0Af(%7B%20a%3A%200%20as%20any%20as%20El%3C'a'%2C%20HTMLAnchorElement%2C%20Children%3E%20%7D)%3B%0D%0Af(%7B%20a%3A%200%20as%20any%20as%20El%3C'a'%2C%20HTMLAnchorElement%2C%20Children.Void%3E%20%7D)%3B

Related Issues:

Activity

  1. falsandtru commented on Feb 22, 2019

    @falsandtru
    ContributorAuthor

    #30010 may fix this.

  2. jack-williams commented on Feb 27, 2019

    @jack-williams
    Collaborator

    Another smaller repro:

    interface Foo<T> {
        prop: T extends unknown ? true : false;
    }
    
    const foo = { prop: true } as const;
    const x: Foo<1> = foo;
    const y: Foo<number> = foo;
    const z: Foo<number> = x; // error

    A smaller repro.

    type Children =
        | Children.Void
        | Children.Text
        | Children.Collection
        | Children.Record;
    
    namespace Children {
        export type Void = undefined;
        export type Text = string;
        export interface Collection extends ReadonlyArray<El> { }
        export type Record = { [field: string]: El; };
    }
    
    interface El<
        C extends Children = Children,
        > {
            children: Relax<C>;
        }
    
    type Relax<C extends Children> = C extends Children.Text ? Children.Text : C;
    
    const a: El = 1 as any as El<Children.Void>; // error

    I think the issue here is that variance markers are marking C in Relax as invariant. This is probably caused by #27697 and the extension to variance computation.

    Even though TypeScript never ends up relating two conditional types in a way the requires invariance of the check types, the effect of variance computation makes the assignment illegal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Needs InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions