Skip to content

Typeguard for union not working after adding a property to the superclass of one of the union members #35953

Closed
@G-Rath

Description

@G-Rath

This issue appeared in eslint-plugin-jest after updating @typescript-eslint to 2.13.0 (from 2.12.0).

TypeScript Version: 3.7.x-dev.201xxxxx

Search Terms:
typeguard, union
Code

export declare enum AST_NODE_TYPES {
  Literal = 'Literal',
  TemplateLiteral = 'TemplateLiteral',
}

export type TSNode = StringLiteral | TemplateLiteral;

export interface BaseNode {
  stuff: null;
}
export interface LiteralBase extends BaseNode {
  raw: string;
  value: boolean | number | RegExp | string | null;
  regex?: {
    pattern: string;
    flags: string;
  };
}

export interface StringLiteral extends LiteralBase {
  type: AST_NODE_TYPES.Literal;
  value: string;
}
export interface TemplateLiteral extends BaseNode {
  type: AST_NODE_TYPES.TemplateLiteral;
  quasis: string[];
}

interface MyStringLiteral<Value extends string = string> extends StringLiteral {
  value: Value;
}

interface MyTemplateLiteral<Value extends string = string>
  extends TemplateLiteral {
  quasis: [Value];
}

type MyStringNode<S extends string = string> =
  | MyStringLiteral<S>
  | MyTemplateLiteral<S>;

declare function isMyStringNode<V extends string>(
  node: TSNode,
  specifics?: V,
): node is MyStringNode<V>;

function foo(argument: TSNode) {
  if (isMyStringNode(argument)) {
    /*
        Expected: argument.type is Literal|TemplateLiteral
        Actual  : argument.type is Literal

        This condition will always return 'false' since the types
        'AST_NODE_TYPES.Literal' and 
        'AST_NODE_TYPES.TemplateLiteral' have no overlap.
    */
    if (argument.type === AST_NODE_TYPES.TemplateLiteral) {
    }
  }
}

This is a real life reproduction - it can be made smaller by removing the generics, BaseNode, etc, but then it becomes counterproductive as you can argue the solution to be "just use StringLiteral directly".

Expected behavior:
No errors

Actual behavior:

if (argument.type === AST_NODE_TYPES.TemplateLiteral) { is marked as an error, as TS believes argument.type can only be AST_NODE_TYPES.Literal (aka it appears to discard the alternative in the union).

This behaviour is tied to the existence of the value: string property on StringLiteral: if you remove that property, everything works as expected (which is what changed between 2.12.0 & 2.13.0: previously Literal was just an interface w/ type - now it's a union of <Type>Literal interfaces; one for each of the values of LiteralBase#value)

Playground Link: https://www.typescriptlang.org/play/?ts=3.8.0-dev.20191231&ssl=1&ssc=1&pln=60&pc=2#code/KYDwDg9gTgLgBAE2AYwDYEMrDsAdgVwFs4BBAZQBUB9AOQHkARAUSooE0AFJsuAbwFgAUHDgAZAJYxgUdKjgBeOAHIJUmaiUAaISIrBCYDFNXTZC5XoNHgJ9VqEBfIUNCRYcGAE8w2CmRoQSOZkMFDiuADmtmYAPnCWhujGkqaoANzOgq7Q8OFqAGboyNgAQugAzsABQQLCcOUw+Pn5AFxwBKjpji7gOXB50oXFYinqZZU4IFK4COVw41WB2LUiMgDubQ1hkRl1AG6y+MBtAEYQEKjA6LhwcQSEJ9K3cABKwBFM4M9b4RHPHV06lgIqAAPxtFYiOBgJJqXCbUK-XZQuD5DARcoI7YRZFwBy7JyCHpuXK4ApFbAhbHROSgaazEZqWQLPg6DzeY6kSi0RgsdhcMgAOhpuIOqCOWKR3SyvXcAygQ18+kSySZtKmeAZC2qyzZXh8bXI1HozFYnG4goS1hFbIAjvgKuJMfVEZEANoAXQJmXlirgAFlPFTfjSADwANUO2DpmrmP0i5njEQAfJN6XNg5Eaaz9lG2pHxcBvUTBL6KQHPFakjZRrII1G07GXdjE66U2yYzM5lXVakcyJ7Y7nW6C0cvdKhPrsIHMxEdaGeJ2GUnW9jU-I2XEZ22w2Rk5uKz2a2qF8ndkIkGhMNh8vhcMgYOIIDcndvsfPw42u83fsmABRsrgSxtH4OraHU5Q+Mg4j5OIyDlOCcDhuBACUbRAUEToVrOH5npkt73o+z6oucf6YBERB4DAIH+EsKH9v0+RwH+r5Bm2OpkVAFGEFRKH0ZCIgAPQAFRsiiIifFBUgIG05GUWSgpTv0cw0jER40mJ4kkA+DpyHAslcfJMCKRyymMqkmTiVCFAABZYcgz4IJIT43Gs4idHAshrOgnhzFgjRQDcSiFKglRKPU4TDDANnYFO5SaSiShGjypr8haNLhdcCBwAlUJJdyJp8uaQrqbWGhwDZ6B7NgQFwBA1VQBgYCCppwmCZpMHMXJPEKUp8j9Vyxq8maAqWsq1plfxmmEiIhIOEAA

Related Issues:

This is probably a duplicate of #31156, but I've created a new issue as I believe this is a more subtle (ideally fixable) issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions