Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 10, 2025

Fixes #62358

JSX fragments (<>...</>) in react-jsx/react-jsxdev modes were not type-checking children, while explicit <Fragment> worked correctly:

// @jsx: react-jsx
import { Fragment } from "@/jsx-runtime";

type JsxElement = string | ((arg: { foo: "bar" }) => void);
interface FragmentProps { children?: JsxElement; }
export const Fragment: (props: FragmentProps) => any;

<Fragment>{({ foo }) => "ok"}</Fragment>;  // ✓ foo: "bar"
<>{({ foo }) => "not ok"}</>;              // ✗ foo: any (before fix)

Changes

Extended fragment factory resolution to modern JSX modes

  • getJSXFragmentType: Added JsxEmit.ReactJSX and JsxEmit.ReactJSXDev to shouldResolveFactoryReference check
  • Previously only JsxEmit.React was checked, causing fragments to return anyType in modern modes

Added contextual typing for fragment children

  • createJsxAttributesTypeFromAttributesProperty: Extract Fragment props type from factory signature and derive children contextual type
  • checkJsxChildren: Accept optional contextual type parameter, apply to fragment child expressions via checkExpressionForMutableLocationWithContextualType
  • Regular JSX elements unchanged - only fragments compute contextual type this way

Fixed error message for modern JSX modes

  • getJSXFragmentType: When using react-jsx/react-jsxdev modes, error messages now correctly reference "Fragment" instead of "React"
  • This ensures accurate error messages when using custom JSX import sources (e.g., @jsxImportSource preact)

Test Coverage

Added jsxFragmentChildrenCheck.tsx validating both syntaxes produce identical type checking.

Updated baselines for existing tests now correctly report error 2879 when Fragment factory is missing in react-jsx modes (previously unreported due to anyType early return), with accurate error messages showing "Fragment" as the expected factory name.

Original prompt

This section details on the original issue you should resolve

<issue_title>JSX Fragments are in-properly typed with <></> syntax, when using react-jsx JSX option</issue_title>
<issue_description>### 🔎 Search Terms

JSX, Fragment, children type

🕗 Version & Regression Information

Bug exists since TypeScript introduces "jsx": "react-jsx" from version 4.1.2 until next version (6.0.0-dev.20250830).

⏯ Playground Link

https://stackblitz.com/edit/stackblitz-starters-sffhmvvz?file=index.tsx

💻 Code

import { Fragment } from "@/jsx-runtime";

<Fragment>
  {"ok"}
  {({ foo }) => "also ok"}
</Fragment>;

<>
  {"ok"}
  {({ foo }) => "not ok"}
  {/* ^ Binding element 'foo' implicitly has an 'any' type. */}
</>;

With following @/jsx-runtime definition:

export const jsx = (...args: unknown[]) => {};

type JsxElement =
  | JsxElementArray
  | undefined
  | string
  | ((arg: { foo: "bar " }) => void);
interface JsxElementArray extends Array<JsxElement> {}

interface FragmentProps {
  children?: JsxElement;
}

export const Fragment = (props: FragmentProps) => {};

and tsconfig:

{
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "jsxImportSource": "@",
    // [...]
  }
}

🙁 Actual behavior

The first JSX expression using <Fragment> pass the type checking while the second JSX using <> syntax reports error due to it's children was typed to any:

index.tsx:10:7 - error TS7031: Binding element 'foo' implicitly has an 'any' type.

10   {({ foo }) => "not ok"}
         ~~~


Found 1 error in index.tsx:10

🙂 Expected behavior

These two JSX expressions should both pass the type checking.

Additional information about the issue

This issue was first mentioned at #50429 and fixed in #59933, but the fix only take effects on "jsx": "react" not "jsx": "react-jsx", while the latter is a more encouraged approach in a modern project.</issue_description>

<agent_instructions>Read the crosslinked issues and PRs, construct a test case that shows the issue and commit it, then work to fix the bug.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@typescript-bot typescript-bot added the For Milestone Bug PRs that fix a bug with a specific milestone label Nov 10, 2025
Copilot AI changed the title [WIP] Fix improper typing for JSX fragments with react-jsx Fix JSX fragment children type checking in react-jsx mode Nov 10, 2025
Copilot AI requested a review from jakebailey November 10, 2025 16:37
Copilot finished work on behalf of jakebailey November 10, 2025 16:37
Copilot AI requested a review from jakebailey November 10, 2025 16:52
Copilot finished work on behalf of jakebailey November 10, 2025 16:52
@jakebailey
Copy link
Member

@typescript-bot test it

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 11, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started ✅ Results
user test this ✅ Started ✅ Results
run dt ✅ Started ✅ Results
perf test this faster ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user tests with tsc comparing main and refs/pull/62743/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Git clone failed"

Otherwise...

Everything looks good!

@typescript-bot
Copy link
Collaborator

@jakebailey
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 1 1 ~ ~ ~ p=1.000 n=6
Symbols 62,370 62,370 ~ ~ ~ p=1.000 n=6
Types 50,386 50,386 ~ ~ ~ p=1.000 n=6
Memory used 195,044k (± 0.93%) 195,639k (± 0.82%) ~ 192,379k 196,454k p=0.471 n=6
Parse Time 1.60s (± 1.23%) 1.60s (± 0.83%) ~ 1.58s 1.62s p=0.720 n=6
Bind Time 0.87s (± 1.18%) 0.88s (± 1.88%) ~ 0.86s 0.90s p=1.000 n=6
Check Time 11.98s (± 0.51%) 11.98s (± 0.59%) ~ 11.86s 12.05s p=0.872 n=6
Emit Time 3.43s (± 4.16%) 3.43s (± 3.30%) ~ 3.29s 3.59s p=0.936 n=6
Total Time 17.89s (± 0.90%) 17.88s (± 0.94%) ~ 17.67s 18.09s p=0.936 n=6
angular-1 - node (v18.15.0, x64)
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 956,047 956,047 ~ ~ ~ p=1.000 n=6
Types 415,881 415,881 ~ ~ ~ p=1.000 n=6
Memory used 1,255,242k (± 0.01%) 1,255,199k (± 0.01%) ~ 1,255,115k 1,255,287k p=0.575 n=6
Parse Time 7.92s (± 0.96%) 8.05s (± 0.98%) +0.13s (+ 1.58%) 7.93s 8.16s p=0.030 n=6
Bind Time 2.27s (± 0.65%) 2.28s (± 0.78%) ~ 2.26s 2.31s p=0.408 n=6
Check Time 38.73s (± 0.37%) 38.66s (± 0.34%) ~ 38.53s 38.85s p=0.224 n=6
Emit Time 17.88s (± 0.41%) 17.91s (± 0.34%) ~ 17.82s 17.96s p=0.574 n=6
Total Time 66.81s (± 0.24%) 66.90s (± 0.24%) ~ 66.71s 67.18s p=0.521 n=6
mui-docs - node (v18.15.0, x64)
Errors 1 1 ~ ~ ~ p=1.000 n=6
Symbols 553,308 553,308 ~ ~ ~ p=1.000 n=6
Types 89 89 ~ ~ ~ p=1.000 n=6
Memory used 828,511k (± 0.01%) 828,475k (± 0.00%) ~ 828,443k 828,528k p=0.173 n=6
Parse Time 10.20s (± 0.71%) 10.22s (± 0.61%) ~ 10.16s 10.33s p=0.809 n=6
Bind Time 2.64s (± 0.61%) 2.65s (± 1.03%) ~ 2.61s 2.67s p=0.567 n=6
Check Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Emit Time 0.34s (± 3.45%) 0.33s (± 1.91%) ~ 0.32s 0.34s p=0.179 n=6
Total Time 13.18s (± 0.72%) 13.20s (± 0.58%) ~ 13.13s 13.34s p=0.809 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,237,278 1,237,285 +7 (+ 0.00%) ~ ~ p=0.001 n=6
Types 259,884 259,884 ~ ~ ~ p=1.000 n=6
Memory used 2,426,021k (± 6.10%) 2,547,602k (±11.94%) ~ 2,365,140k 3,093,245k p=0.471 n=6
Parse Time 5.15s (± 1.23%) 5.16s (± 1.42%) ~ 5.08s 5.29s p=0.688 n=6
Bind Time 1.77s (± 0.77%) 1.78s (± 1.37%) ~ 1.75s 1.82s p=0.934 n=6
Check Time 35.20s (± 0.80%) 35.37s (± 0.88%) ~ 34.95s 35.90s p=0.471 n=6
Emit Time 3.02s (± 0.80%) 2.98s (± 1.41%) ~ 2.93s 3.05s p=0.128 n=6
Total Time 45.15s (± 0.49%) 45.29s (± 0.78%) ~ 44.88s 45.92s p=0.575 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,237,278 1,237,285 +7 (+ 0.00%) ~ ~ p=0.001 n=6
Types 259,884 259,884 ~ ~ ~ p=1.000 n=6
Memory used 3,159,630k (± 0.02%) 3,039,066k (± 9.72%) ~ 2,435,432k 3,160,770k p=1.000 n=6
Parse Time 6.84s (± 1.09%) 6.74s (± 1.63%) ~ 6.57s 6.87s p=0.093 n=6
Bind Time 2.16s (± 1.34%) 2.15s (± 0.95%) ~ 2.12s 2.18s p=1.000 n=6
Check Time 42.93s (± 0.27%) 42.72s (± 0.45%) ~ 42.34s 42.85s p=0.066 n=6
Emit Time 3.49s (± 3.17%) 3.51s (± 2.04%) ~ 3.44s 3.59s p=0.936 n=6
Total Time 55.42s (± 0.48%) 55.13s (± 0.60%) ~ 54.53s 55.42s p=0.128 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 262,523 262,530 +7 (+ 0.00%) ~ ~ p=0.001 n=6
Types 104,049 104,049 ~ ~ ~ p=1.000 n=6
Memory used 440,751k (± 0.01%) 440,749k (± 0.01%) ~ 440,686k 440,823k p=0.810 n=6
Parse Time 3.52s (± 0.98%) 3.52s (± 0.76%) ~ 3.48s 3.56s p=0.936 n=6
Bind Time 1.33s (± 0.74%) 1.32s (± 0.80%) ~ 1.30s 1.33s p=0.054 n=6
Check Time 19.11s (± 0.37%) 19.11s (± 0.42%) ~ 19.02s 19.26s p=0.809 n=6
Emit Time 1.53s (± 1.23%) 1.53s (± 0.76%) ~ 1.51s 1.54s p=0.807 n=6
Total Time 25.48s (± 0.37%) 25.47s (± 0.25%) ~ 25.44s 25.60s p=0.933 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 72 72 ~ ~ ~ p=1.000 n=6
Symbols 225,386 225,386 ~ ~ ~ p=1.000 n=6
Types 94,304 94,304 ~ ~ ~ p=1.000 n=6
Memory used 370,136k (± 0.09%) 370,164k (± 0.09%) ~ 369,911k 370,793k p=0.575 n=6
Parse Time 2.83s (± 0.92%) 2.81s (± 1.52%) ~ 2.74s 2.86s p=0.684 n=6
Bind Time 1.60s (± 0.88%) 1.61s (± 0.85%) ~ 1.59s 1.62s p=0.461 n=6
Check Time 16.51s (± 0.18%) 16.45s (± 0.18%) -0.06s (- 0.36%) 16.41s 16.49s p=0.016 n=6
Emit Time 0.00s 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 20.94s (± 0.17%) 20.88s (± 0.28%) ~ 20.79s 20.95s p=0.065 n=6
vscode - node (v18.15.0, x64)
Errors 5 5 ~ ~ ~ p=1.000 n=6
Symbols 4,023,575 4,023,575 ~ ~ ~ p=1.000 n=6
Types 1,263,832 1,263,832 ~ ~ ~ p=1.000 n=6
Memory used 3,806,262k (± 0.00%) 3,806,473k (± 0.01%) ~ 3,806,123k 3,806,839k p=0.230 n=6
Parse Time 15.45s (± 0.53%) 15.48s (± 0.79%) ~ 15.28s 15.59s p=0.630 n=6
Bind Time 5.07s (± 1.99%) 5.04s (± 0.60%) ~ 5.01s 5.09s p=0.808 n=6
Check Time 104.26s (± 3.63%) 107.51s (± 4.09%) ~ 102.87s 112.63s p=0.065 n=6
Emit Time 40.20s (±14.34%) 39.96s (±27.07%) ~ 31.60s 61.07s p=0.298 n=6
Total Time 164.98s (± 2.45%) 168.00s (± 5.66%) ~ 161.47s 186.87s p=0.689 n=6
webpack - node (v18.15.0, x64)
Errors 40 40 ~ ~ ~ p=1.000 n=6
Symbols 379,225 379,225 ~ ~ ~ p=1.000 n=6
Types 166,383 166,383 ~ ~ ~ p=1.000 n=6
Memory used 538,103k (± 0.01%) 538,097k (± 0.01%) ~ 537,989k 538,175k p=0.810 n=6
Parse Time 4.60s (± 0.36%) 4.60s (± 0.76%) ~ 4.57s 4.66s p=1.000 n=6
Bind Time 1.96s (± 1.54%) 1.96s (± 1.19%) ~ 1.92s 1.99s p=0.371 n=6
Check Time 22.85s (± 0.43%) 22.83s (± 0.48%) ~ 22.71s 23.03s p=0.575 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 29.41s (± 0.38%) 29.39s (± 0.35%) ~ 29.26s 29.57s p=0.575 n=6
xstate-main - node (v18.15.0, x64)
Errors 30 30 ~ ~ ~ p=1.000 n=6
Symbols 690,485 690,641 +156 (+ 0.02%) ~ ~ p=0.001 n=6
Types 208,757 208,871 +114 (+ 0.05%) ~ ~ p=0.001 n=6
Memory used 586,488k (± 0.03%) 586,497k (± 0.02%) ~ 586,420k 586,754k p=0.689 n=6
Parse Time 5.20s (± 0.52%) 5.19s (± 0.71%) ~ 5.15s 5.24s p=0.469 n=6
Bind Time 1.67s (± 1.54%) 1.68s (± 1.06%) ~ 1.66s 1.70s p=0.682 n=6
Check Time 25.83s (± 2.00%) 26.14s (± 2.02%) ~ 25.18s 26.70s p=0.298 n=6
Emit Time 0.01s (±109.43%) 0.01s (±77.38%) ~ 0.00s 0.01s p=0.640 n=6
Total Time 32.70s (± 1.63%) 33.02s (± 1.64%) ~ 32.06s 33.62s p=0.336 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top 400 repos with tsc comparing main and refs/pull/62743/merge:

Everything looks good!

@jakebailey
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 11, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started

@jakebailey
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 11, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/166506/artifacts?artifactName=tgz&fileId=6A682EAE4D1D38D9F14653D0C3D34184B74CAB8900DC1B4D91724814333BD3A102&fileName=/typescript-6.0.0-insiders.20251111.tgz"
    }
}

and then running npm install.

@ArnaudBarre
Copy link

ArnaudBarre commented Nov 11, 2025

I just tested the build and I now get an error if an object is passed as a child of a shorthand fragment when using react-jsx 🎉

Running on 560 tsx files, it caught the same two cases than my custom linter rule (opaque type which is a number at runtime)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Milestone Bug PRs that fix a bug with a specific milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSX Fragments are in-properly typed with <></> syntax, when using react-jsx JSX option

4 participants