Skip to content

Behavior difference: callback contextual typing — tsc@5.9.3 passes, tsc@6.0.3 / tsgo fail #3788

Description

TL;DR: On the same source and same pnpm install, typescript@6.0.3 and tsgo emit TS7006 / TS7031 on callback parameters that typescript@5.9.3 types correctly. The condition that surfaces it is pnpm's enableGlobalVirtualStore symlink layout — which tsc@5.9.3, Node, and bundlers all handle without issue.

Steps to reproduce

Repro repo (single file, single tsconfig): https://github.com/dougludlow/ts-callback-inference-bug

import type { Meta, StoryObj } from '@storybook/react-vite';
import type { ElementType, ReactNode } from 'react';

interface LabelProps { id: string; children: ReactNode; }
interface WidgetProps { metric: number; slots?: { label?: ElementType<LabelProps> }; }

declare function Widget(props: WidgetProps): ReactNode;

type Story = StoryObj<typeof Widget>;
const meta: Meta<typeof Widget> = { component: Widget };
export default meta;

export const CustomLabel: Story = {
	args: {
		metric: 50,
		slots: {
			label: ({ id, children }) => <span id={id}>{children}</span>,
		},
	},
};

Bare tsconfig.json (no strict, no noImplicitAny). pnpm-workspace.yaml contains enableGlobalVirtualStore: true. Commenting that line out makes all three compilers pass on the same source.

Behavior with typescript@5.9.3

Passes.

Behavior with typescript@6.0.3

repro.tsx(29,14): error TS7031: Binding element 'id' implicitly has an 'any' type.
repro.tsx(29,18): error TS7031: Binding element 'children' implicitly has an 'any' type.

Behavior with tsgo (@typescript/native-preview@7.0.0-dev.20260509.2)

Same TS7031 errors as typescript@6.0.3.

Additional context

The repro is the smallest shape I could isolate; in real code the same failure pattern surfaces in Storybook story args, material-react-table cell/header callbacks, Vitest it.each destructuring, MUI event handlers, and Express middleware — anywhere a callback's contextual type comes through a generic from a third-party .d.ts.

preserveSymlinks: true is not a fix. The only workaround we've found is annotating each callsite (({ id, children }: LabelProps)).

Pnpm-side report of the same trigger: pnpm/pnpm#9739.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions