Repository: TypeScript
Issue: Evolving any behavior for local function variables
Labels: Suggestion,In Discussion

## Search Terms

- evolving callback

## Suggestion

When you declare a local variable whose type is implicitly `any`, it has an opportunity to ["evolve"][3] before triggering an implicit `any` error:

```ts
let x = null;  // type is any, but not an error
if (Math.random() < 0.5) x = 'hello';
x;  // type is string | null
```

My suggestion is to allow a similar behavior for functions whose parameters implicitly have an `any` type. This is currently an error, but there's nothing wrong with the code:

```ts
{
  const mapFn = x => x * x;
  // Parameter 'x' implicitly has an 'any' type. (7006)
  [1, 2, 3].map(mapFn);
}
```

If `x`'s type were allowed to "evolve" from `any` to `number`, this correct code would type check.

Anders doesn't like "spooky action at a distance." But perhaps some limited, self-contained action within a block could solve this problem while avoiding the spookiness?

## Use Cases

The loss of contextual types when you factor out a variable is a perennial pain point in TypeScript. See, for example:

- [How TypeScript Breaks Referential Transparency][1]
- [React+TypeScript Cheatsheet][2]

This is felt most acutely with React components, where factoring out a function often forces you to dig through type declarations for fairly complicated event types. See below.

## Examples

The `Array.prototype.map` example above is one.

Here's an example with a React component. An inline handler requires no explicit types:

```ts
function LoggedCell(props: {cellText: string}) {
  return (
    <td onClick={e => {
        e.stopPropagation();
        // do something
      }}>{props.cellText}</td>
  );
}
```

Factoring out the callback into a local variable produces a no implicit any error:

```ts
function LoggedCell(props: {cellText: string}) {
  const clickFn = e => {
    // Parameter 'e' implicitly has an 'any' type.ts(7006)
    e.stopPropagation();
    // .. do something ...
  };

  return (
    <td onClick={clickFn}>{props.cellText}</td>
  );
}
```

You can dig up the parameter type to fix this, but it's a mouthful:

```ts
function MyComponent() {
  const clickFn = (e: React.SyntheticEvent<HTMLTableDataCellElement>) => {
    e.stopPropagation();
    // .. do something ...
  };

  return (<td onClick={clickFn}>Table Cell</td>);
}
```

There's nothing really wrong with the middle version. I think it should work.

Caveats:

- Would this mean the type checker has to go back and type check the function body? Is that possible? Would that break lots of other things?
- If you used the callback in two places, would its type have to be the union of the two? Evolving any has this behavior, e.g. if you push a `number` and a `string` onto an array, its type becomes `(number|string)[]`.
- Is it OK to return the function? Or let its inferred type "escape" in some other way?
- Like the current evolving any, this probably wouldn't work in functions (#35132). So you couldn't use it in a React component that calls `map`, for example:

  ```ts
  contents.map(cell => <td onClick={clickFn}>{cell}</td>);
  ```

## Checklist

My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code

This would allow some code that triggers a type error now to pass. Not sure if that's what's meant by "breaking." The code that passes would be code that works correctly at runtime.

* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

[1]: https://blog.logrocket.com/how-typescript-breaks-referential-transparency-7a82c0345f2c/
[2]: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/README.md#forms-and-events
[3]: https://effectivetypescript.com/2020/03/09/evolving-any/