Repository: TypeScript
Issue: Infer JSX children as literal
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

JSX children string literal inference 

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  - This technically issues more errors, but in a semantically more correct way.
* [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, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

JSX children, especially JSXText (but preferably simple expressions as well), should be inferred as string literal instead of `string`.

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

[Playground](https://www.typescriptlang.org/play?ssl=5&ssc=50&pln=1&pc=1#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wG4AocgEyTQBsUi5MBXAOw2AjbgGFcwAHgAqcJAA8YSNlQDOcWTCjA2AcwB8ACjA4wsgFxwA3nDQALYHSpE2h4QBo4ANxR0WSA4lrQqIx4uU1dTgAXwBKQwApAGUADQA6AFE6JBBpGAo0bkU4FDgAXj4BTRNzS2tpQwAiTAgIKscXNw9DEwAjRmqUKtDwzOz4NoK4QX5wZ1d3WXyjds64Ku7e9VqIQQB6MbB1CiA)

```tsx
declare function Comp<T extends string>(props: { children: T, values: Record<T, string> }): JSX.Element;
const a = Comp({ children: "foo", values: { bar: "a" }});
const b = <Comp values={{ bar: "a" }}>foo</Comp>;
```

The first example correctly gives an error; the second one compiles fine, because `Comp` is instantiated as `Comp<string>` instead of `Comp<"foo">`. However, the two calls should be functionally equivalent.

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

I'm building an [interpolate](https://docusaurus.io/docs/docusaurus-core#interpolate-1) API and I want to achieve type safety.

The types are defined as:

```ts
export type ExtractInterpolatePlaceholders<Str extends string> =
  Str extends `${string}{${infer Key}}${infer Rest}`
    ? Key | ExtractInterpolatePlaceholders<Rest>
    : never;

export type InterpolateValues<Str extends string, Value extends ReactNode> = {
  [key in ExtractInterpolatePlaceholders<Str>]: Value;
};

export function interpolate<Str extends string>(
  text: Str,
  values?: InterpolateValues<Str, string | number>,
): string;

export default function Interpolate<Str extends string>(
  props: {
    children: Str;
    values?: InterpolateValues<Str, ReactNode>;
  },
): JSX.Element;
```

And users would use it as:

```tsx
const title = interpolate(
  "{pageTitle} | {siteTitle}",
  { pageTitle: "Introduction", siteTitle: "My site" }
);
const heading = (
  <Interpolate
    values={{ pageTitle: "Introduction", siteTitle: "My site" }}>
    {"{pageTitle} | {siteTitle}"}
  </Interpolate>
);
```

While the imperative function form is well-typed, the component form is not. In order to make it strict, I have to do:

```tsx
const heading = (
  <Interpolate
    values={{ pageTitle: "Introduction", siteTitle: "My site" }}>
    {"{pageTitle} | {siteTitle}" as const}
  </Interpolate>
);
```

Which I believe is unnecessary.