Skip to content

5.0: const-like inference is not preserved unless a mapped type is usedΒ #53307

Description

Bug Report

πŸ”Ž Search Terms

const type, validator type

πŸ•— Version & Regression Information

5.0.2

  • I was unable to test this on prior versions because const type parameters are a new feature

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => {
  return o;
};
const t = thing({foo: ''});
// is inferred as { foo: string } ❌

// BUT, when adding a mapped type to NotEmpty it works
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : {
  [K in keyof T]: T[K] // ⬅️
};
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => {
  return o;
};
const tMapped = thingMapped({foo: ''});
// is inferred as { foo: "" } βœ…

πŸ™ Actual behavior

When using a non-mapped validator type, the "valid" branch does not preserve the const-like inference unless the type is mapped over.

πŸ™‚ Expected behavior

A mapped type is not needed to preserve const-like inference.

Activity

  1. Andarist commented on Mar 17, 2023

    @Andarist
    Contributor

    Working case:

    • the apparent type of contextual type for this object is { [K in keyof O]: O[K]; }
    • thus the type for this property becomes O["foo"]
    • isConstTypeParameterContext is able to return true based on this information

    Not working case:

    • the apparent type of contextual type for this object is Record<string, any>
    • thus the type for this property becomes any
    • isConstTypeParameterContext return false based on that
  2. conorbrandon commented on Mar 17, 2023

    @conorbrandon
    Author

    Interesting! Thanks for taking a look.

    I did find it a bit curious that NotEmptyMapped unfortunately doesn't seem to actually error for an empty object, and instead returns Record<string, any>. Is that possibly related?

    Both correctly error if the check is instead changed to {} extends T, but have the same const-like inference mismatch.

    Finally if the check is T extends Record<string, never>, both don't use const-like inference and NotEmptyMapped doesn't error on an empty object, but NotEmpty does.

    I think that's all the ways I can think of to check for an empty object...

  3. ahejlsberg commented on Mar 17, 2023

    @ahejlsberg
    Member

    It looks like we need to probe types a little deeper in isConstTypeVariable.

  4. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions