Skip to content

TypeScript falls back to generic constraint instead of inferred type when object contains function propertiesΒ #62204

@geleto

Description

@geleto

πŸ”Ž Search Terms

'function property generic parameter inference', 'function generic parameter constraint', 'function property generic parameter'

πŸ•— Version & Regression Information

  • This is the behavior in version 6.0.0-dev.20250805, 5.9.2, 5.0.4 and 4.4.4

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.9.2#code/GYVwdgxgLglg9mABAFQKYGcoGEHBgc0QB4AoRclHMPQ1ADylTABN1EBvRAQwH4AubmACeAGkQAjfoNGIIUrsMQBfEgD4AFGQoRcBAcio0RW8o0wCA1qiFxgiAPIBbGFCIHd+MQHIuXxAB9EL3EvVUR6RhY2MFQAN1QAJ0QeRCgEkFREAWAuABt0VBIASg4TWQR0OFzUADpcuHx1HWoCMTMoIoBuEhUSAHo+lAALGDYAdzgEizYdBITUaFyhEjRMQwJ1djKuAQAiIdRc+t3jCgkBAEYAJgBmYyU29NQuxAHEQFByRAA5OHC5ycQAFpynMFlAlogYNREmwEBCfH4FMwgiFEAAHBJwNGJWAYEj9QbIEZsHIwfKQyCTeaLITkFYYbAeTbbPYHI5wE5lcTZcDQeBIdQlTjzKAgBJIa43ZT3R4ZF5vQAy5IgAKL-BICACCCXwIEcTCgiFsqSE2KCaQyflGiDAcANXHQ6AIYC44mqqV+aK4CS4esYSSNUBNmS8OXyqC8+JI9DRkwNgdNACEuMwAGp5GDMLhQSZudb4MIAXkQAAMvAASdhWGx2dwtQgAMkQmASUPwSi8xe6UboMYSBtAkFgCBQDLzV2IZVrNHCDCYrA43AEChkkiXwjEcjXtJUGjKzRo+jzpwo7QESdT6cz2YSuY8qmMRQEsTgGdKZ2alWqdQaTQ8bQZXQ9PiqyMnWVzMmcOyIPshzHMe5Dcogko9GIux0OhuzyoMSqqpi6pBGhGF+DadoOk6LputmBEIrsATQcEXi7H4v6gtARTAaOHjgVskGsrBHJiFyPKDvyiCCguIpihKtzKCh0HoXQmGdK82EqmqAiMQpTHWra3BkfgzqupkVGMTRdFMSEtGBExECmWJUKzNSBrAg5uQgMwGBBLZ7EkEAA

πŸ’» Code

function TestConfig <
    TConfig extends { a?: any, b?: any, c?: any }
>(
    config: TConfig,
    test: keyof Omit<TConfig, 'a' | 'b'> extends never ? true : false
) {
    console.log(config, test);
}

// Example 1: This works correctly
TestConfig({
    a: "hello",
    b: 123,
}, true); // βœ… No error - correctly infers only 'a' and 'b' properties

// Example 2: This fails incorrectly  
TestConfig({
    a: "hello",
    b: function () { return 123 },
}, true); // ❌ Error: Argument of type 'true' is not assignable to parameter of type 'false'


export type BadValidator<TConfig> = `'${keyof TConfig & string}'`;

export function TestConfig2 <
    TConfig extends { a: any, b?: any, c?: any }
>(
    config: TConfig,
    test: BadValidator<TConfig>,
): void {
    console.log(config, test);
}

// Example 3 - correct
TestConfig2({
    a: "hello",
    b: 123
}, "xxx"); // ❌ Error: '"xxx"' not assignable to '"'a'" | "'b'"' (correct)

// Example 4 - incorrect
TestConfig2({
    a: "hello", 
    b: function () { return 123 }
}, "xxx"); // ❌ Error: '"xxx"' not assignable to '"'a'" | "'b'" | "'c'"' (incorrect - includes 'c')

πŸ™ Actual behavior

  • Conditional types (Example 2): When an object contains function properties, TypeScript falls back to the generic constraint type ({ a?: any, b?: any, c?: any }), causing keyof Omit<TConfig, 'a' | 'b'> to include 'c' and not extend never, making the test parameter require false instead of true.

  • Template literal types (Example 4): When an object contains function properties, TypeScript uses the constraint keys instead of the actual object keys, causing BadValidator<TConfig> to become "'a'" | "'b'" | "'c'" instead of just "'a'" | "'b'".

πŸ™‚ Expected behavior

Conditional types (Example 2): Both objects should be inferred as having only the properties they actually contain., Both objects only have 'a' and 'b' properties, so keyof Omit<TConfig, 'a' | 'b'> should extend never, making the test parameter accept true in both cases.

Template literal types (Example 4): Both objects should generate the same template literal union. Both objects only have 'a' and 'b' properties, so BadValidator<TConfig> should be "'a'" | "'b'" in both cases.

Additional information about the issue

Description

When an object contains function properties, TypeScript's type inference incorrectly falls back to the generic constraint instead of using the actual inferred type. This affects conditional types, template literal types, and other type-level computations.

Impact

This bug prevents implementing compile-time validation that uses conditional types or template literals to analyze the structure of objects with function properties.

Workaround

Adding as const forces correct inference:

TestConfig2({
    a: "hello",
    b: function () { return 123 },
} as const, true); // βœ… Works correctly

Unfortunately this is not a good developer experience for a public library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions