Skip to content
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

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

Open
5 tasks done
lazytype opened this issue Nov 4, 2021 · 4 comments
Open
5 tasks done
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@lazytype
Copy link

lazytype commented Nov 4, 2021

Suggestion

πŸ” Search Terms

rest spread element destructure destructuring discriminated union narrow refine

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

A natural extension of #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
Copy link
Member

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

@lazytype
Copy link
Author

lazytype commented Nov 4, 2021

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
Copy link
Member

Can confirm this looks like something React devs do

@andrewbranch andrewbranch added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Nov 4, 2021
@mhofman
Copy link

mhofman commented Jan 29, 2022

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
"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
{
  "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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants