microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.55k stars 12.43k forks source link

Control flow analysis for destructured rest element of discriminated union #46680

Open lazytype opened 2 years ago

lazytype commented 2 years ago

Suggestion

πŸ” Search Terms

rest spread element destructure destructuring discriminated union narrow refine

βœ… Viability Checklist

My suggestion meets these guidelines:

⭐ Suggestion

A natural extension of https://github.com/microsoft/TypeScript/pull/46266 would be to support the following

type Action =
    | { kind: 'A', payload: number }
    | { kind: 'B', payload: string };

function example({ kind, ...rest }: Action) {
    if (kind === 'A') {
        rest.payload.toFixed();
    }
    if (kind === 'B') {
        rest.payload.toUpperCase();
    }
}

πŸ“ƒ Motivating Example

πŸ’» Use Cases

ahejlsberg commented 2 years ago

Yeah, might be nice to support. Though it's an expensive way to do it as you're incurring an extra object allocation.

lazytype commented 2 years ago

Yep, but par for the course for contexts like React where you might extract some props and forward the rest

import React from 'react'

type Props =
  | ({ as: "div" } & React.ComponentPropsWithRef<"div">)
  | ({ as: "span" } & React.ComponentPropsWithRef<"span">)

function Component({ as, ...rest }: Props) {
  if (as === 'div') {
    return <div {...rest} />
  }
  if (as === 'span') {
    return <span {...rest} />
  }

  // I think something like https://github.com/microsoft/TypeScript/issues/30581 would let us just do:
  // return <as {...rest} />
}
andrewbranch commented 2 years ago

Can confirm this looks like something React devs do

mhofman commented 2 years ago

FYI, this is not only a React pattern, we would like to be able to write something like the following, based on #47190:

type Message = {
 method: string,
 args: unknown[],
 result?: string,
};
type KernelDeliveryMessage = [tag: 'message', target: string, msg: Message];
type KernelDeliveryNotify = [tag: 'notify', resolutions: string[] ];

type KernelDeliveryObject = KernelDeliveryMessage | KernelDeliveryNotify;

declare function translateMessage(target: string, msg: Message): any;
declare function translateNotify(resolutions: string[]): any;

type KernelDeliveryToVatDelivery = (...args: KernelDeliveryObject) => any;

const kernelDeliveryToVatDelivery:KernelDeliveryToVatDelivery = (type,...args) => {
    switch (type) {
      case 'message':
        return translateMessage(...args);
      case 'notify':
        return translateNotify(...args);
      default:
        throw new Error(`unknown kernelDelivery.type ${type}`);
    }
}
Output ```ts "use strict"; const kernelDeliveryToVatDelivery = (type, ...args) => { switch (type) { case 'message': return translateMessage(...args); case 'notify': return translateNotify(...args); default: throw new Error(`unknown kernelDelivery.type ${type}`); } }; ```
Compiler Options ```json { "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", "module": "ESNext", "moduleResolution": "node" } } ```

Playground Link: Provided