Skip to content

Improve inference in the presence of context-sensitive expressionsΒ #47599

Open
@RyanCavanaugh

Description

@RyanCavanaugh

Suggestion

πŸ” Search Terms

contextually context-sensitive generic inference unused parameter method object literal

βœ… 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

When generic inference is gathering candidates, context-sensitive expressions should still be considered if it's possible to do so without observing a type parameter from the call.

πŸ“ƒ Motivating Example

declare function callIt<T>(obj: {
    produce: (n: number) => T,
    consume: (x: T) => void
}): void;

// Works
callIt({
    produce: () => 0,
    consume: n => n.toFixed()
});

// Fails for no obvious reason
callIt({
    produce: _a => 0,
    consume: n => n.toFixed()
});

// Fails for no obvious reason
callIt({
    produce() {
        return 0;
    },
    consume: n => n.toFixed()
});

πŸ’» Use Cases

Background: expressions can be "context-sensitive" for the purposes of inference -- this is a syntactic property of an expression that tells us whether or not an expression's type could be dependent on the inference of its contextual type. For _a => 0, this is true because the return expression might depend on _a (including in indirect ways). For produce() {, it's context-sensitive because (unlike an arrow function) it takes its parent object type for this, so could depend on the type parameter via a reference of this.

When it turns out that the type of the expression is not actually dependent on the inference of the type parameter (which would represent a true circularity, thus more understandably non-working), then this just looks busted for no obvious reason. This is a continuous source of surprise and annoyance. (TODO: link user reports)

Discussion

There are a few implementation strategies we could try, with varying trade-offs

  • Proceed to do structural inference on context-sensitive expressions but mark the type parameters symbols as "off limits" with logic to back out. This is likely impractically invasive
  • Perform a more robust check to determine "true" context-sensitivity -- for example, produce: _a => 0 should not be contextually sensitive because the relational target (n: number) => T does not use T in a covariant position. There are multiple ways we could do this
  • Add an optional intermediate pass when inference collected no candidates when ignoring context-sensitive expressions where we go and collect from them anyway. This pass could safely fix parameters since they'd be going to unknown. This entire bullet point might be wrong.
  • As a stopgap, consider function expressions to not be context-sensitive if all of their return expressions can be trivially seen to not depend on earlier lines of code (e.g. are literals, literals of literals, null, or undefined). This might fix a surprisingly large class of reports
  • As a stopgap, consider an object literal method to not be context-sensitive if it doesn't reference this. I believe we already have code for this and it would fix another large class of surprises.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Experimentation NeededSomeone needs to try this out to see what happensHelp WantedYou can do thisSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions