Skip to content

Conditional type inference fails when generic parameter has Readonly<T> in function parameter position #62720

@privatenumber

Description

@privatenumber

🔎 Search Terms

Readonly generic parameter conditional type inference conditional type matching with Readonly parameter generic constraint Readonly intersection type mapped type readonly conditional inference failure Readonly parameter affects return type inference

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about conditional types,
    generic inference, mapped types, and readonly

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/PTAEF5K7NBlApgFwK4AdQGMD2ATBEMRRAUEgJ5oEAqlCAYigHaZICW2TAPAErIoAnJrSoRQzANZNsAdyYA+MQAoAdGoCGAgOYBnAFyh1TcgG0AugEoIivqiEiEAbjJ1Q9ADbrdYgN4lQAaAmAGaeWgBy6gC2CAY6SAJsTFpmBn6BGaAUVAYOjCzsnM6ZgfjB6ijuSAD8BpLScsWBAL7OrS6ieWFccJgAFghR6jqgCAAeSAhMuCMeXjqK4KDpgSFhoEmgEgjk2MHw-YPDqQcDQzprXmajE1Mzy1l0uXT5rBzcScEIAqDU8o6gZr+Eqgaq-YElAxMBAAN2+JHaJBAhGIUFAAHVsAIJCMZGwkH1sCgkOIdEktKA+OpcJx3OQuItUUySCR8JhPAICMFmG9OKBgkxMdjyVwAPJoQpMEbjSbTWZhBZKCGgbAS976UDiyU6EgWZ5UOZaMVqzgLZw4KUkzk6SpIIUScliAX28lKFYBABG2Gw7gQRkNaUeOVAACFvb6jICADTKr0+v1MQ0AEQQ5VtaWVGWysVD4YTMZBoDKFSqBnK7h0CALLRjzQszmttpdyRUcYjibCSLAhcCAD1qiRG1Vm1pW3n-WEU2mql2e32ByzZ0y0SGUBSZAMmKTHVSaUw6QyUcuYCy2RyuTzJfymKujVr1TdZfdDYrlartQZd7T6ffTfJdfqDDdL+Ur-CQFrxKAQ5ILeTo3mubqxuOHZeIG2YGGG8aRs01aeshyapiWSAZoW6G5lhTC4Zkxbpvy6gVlWyo4Qi9aDggNpVLeY4UYas5zqA-ZsRxMFrtx7YEdOSB8XOglAA

💻 Code

// ============ Setup code ==================
type TypeFunction<ReturnType = unknown> = (...args: any[]) => ReturnType;
type Flags = {
    [flagName: string]: {
        type: TypeFunction;
        default?: unknown;
    };
};
type TypeFlag<Schemas extends Flags> = {
    [flag in keyof Schemas]: Schemas[flag] extends { type: TypeFunction<infer T>; }
        ? T
        : never
};

// ============ Works without using Readonly<> ==================

declare function fnWorking<Options extends Flags>(
    options: Options
): TypeFlag<Options>;
const resultWorking = fnWorking({
    booleanFlag: { type: Boolean },
    booleanFlagDefault: {
        type: Boolean,
        default: false,
    },
});
resultWorking.booleanFlag
//            ^? (property) booleanFlag: boolean
resultWorking.booleanFlagDefault
//            ^? (property) booleanFlagDefault: boolean



// ============ Bug when using Readonly<> ==================

declare function fnBug<Options extends Flags>(
    options: Readonly<Options>
): TypeFlag<Options>;
const resultBug = fnBug({
    booleanFlag: { type: Boolean },
    booleanFlagDefault: {
        type: Boolean,
        default: false,
    },
});
resultBug.booleanFlag
//            ^? (property) booleanFlag: boolean
resultBug.booleanFlagDefault
//            ^? (property) booleanFlagDefault: never

🙁 Actual behavior

When a generic function has Readonly<T> in the parameter position, conditional type matching in the return type fails even though T itself is not readonly:

declare function fnBug<Options extends Flags>(
    options: Readonly<Options>
): TypeFlag<Options>; // Options is NOT readonly here

type Test = typeof resultBug. booleanFlagDefault; // Returns `never` (incorrect)

The conditional type Schemas[flag] extends { type: TypeFunction<infer T> } fails to match and falls through to the never branch.

This happens even though:

  1. Options itself is not Readonly in the return type TypeFlag<Options>
  2. The inferred type from the argument has the correct structure
  3. The same pattern works perfectly without Readonly<Options> in the parameter

🙂 Expected behavior

The conditional type should match successfully regardless of whether the parameter is Readonly<Options> or Options, since the return type TypeFlag<Options> uses Options directly (not Readonly<Options>).

Expected result:

type Test = typeof resultBug. booleanFlagDefault; // Should be `boolean`

The Readonly modifier in the parameter position should not affect type inference in the return type when the generic parameter Options itself is not readonly.

Additional information about the issue

Structural typing inconsistency

The types are structurally compatible:
{ type: BooleanConstructor; default: false } should match { type: TypeFunction<infer T> }

but TypeScript's conditional type matching fails when Readonly<Options> appears in the parameter.

Workaround exists

Adding an intersection to the conditional pattern fixes the issue, proving this is a pattern-matching bug rather than a semantic issue:

Schemas[flag] extends ({ type: TypeFunction<infer T> } & Record<PropertyKey, unknown>)

This workaround doesn't change the logical meaning but helps TypeScript's matcher recognize the types as compatible.

TS Playground

Real world Impact

This bug affects any library that wants to accept readonly parameters while using conditional type inference, forcing authors to use syntactic workarounds that pollute the type definitions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions