Skip to content

Control flow analysis for dependent function parameters "destructured" from a tuple type #46658

Closed
@Andarist

Description

@Andarist

Suggestion

🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

dependent arguments, parameters

This issue is very related to mine here, but I don't believe it's the same. That issue propose some bigger changes to TS - while I'd only like to "reuse" the, just landed, logic for the situation described below.

✅ 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

It's today possible to mimic dependent function parameters with tuples, spreads & co. Using such function parameters is clunky though because one can't destructure the arguments tuple without losing nice control flow analysis.

Since control flow analysis for destructured properties from unions has just landed on the main branch ( #46266 ), I think it would be great to extend the logic to cover this situation.

I've looked briefly at how this works for the newly introduced control flow analysis and I think the problem is that the information about the identifier coming from the arguments list is not propagated to getCandidateDiscriminantPropertyAccess. So it has no chance of handling this "correctly". If we could pass this info there and then check the signature type, if it's spread+tuple based then we could apply the same logic as for binding patterns.

📃 Motivating Example

I've added a test case for this here: Andarist@3e87dec

type Args = ['A', number] | ['B', string]

declare function fn10(cb: (...args: Args) => void): void

fn10((kind, data) => {
    if (kind === 'A') {
        data.toFixed();
    }
    if (kind === 'B') {
        data.toUpperCase();
    }
})

Currently, with this technique, one could do this to keep the control flow analysis working:

type Args = ['A', number] | ['B', string]

declare function fn10(cb: (...args: Args) => void): void

fn10((...args) => {
    if (args[0] === 'A') {
        args[1].toFixed();
    }
    if (args[0] === 'B') {
        args[1].toUpperCase();
    }
})

but this ain't super friendly to the user.

💻 Use Cases

In XState (a state machine library) we have actions (can be thought of as functions/callbacks) that can be declared like this:

assign((context, event) => {
  // this particular action can return a partial update to the context value
  return {}
})

Since XState is a state machine library some combinations of context & event types are impossible. With this feature in place, we could explore computing valid combinations and maybe (since the task ain't trivial) we could narrow down types of the context parameter based on different event.types. So for instance, there is a chance that this would become possible:

type Context = { userId: string | null }

assign((context, event) => {
  if (event.type === 'CHANGE_PASSWORD') {
    // `context` could be narrowed down to `{ userId: string }` if we could figure out how to create compute proper parameter tuples
  }
  return {}
})

cc @ahejlsberg

Metadata

Metadata

Assignees

Labels

In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions