Skip to content

Transformers in a map generated via a Mapped Type with Key Remapping are erroneously inferred to return anyΒ #57097

Description

πŸ”Ž Search Terms

"mapped types", "key remapping", "indexed access type"

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried (5.2.2, 5.3.2, 5.4.0-dev.20240119), and I reviewed the FAQ.

⏯ Playground Link

https://www.typescriptlang.org/play?target=99&ts=5.4.0-dev.20240119#code/KYDwDg9gTgLgBDAnmYcByEAmwCSA7GYKAMwEMBjYAHgBkBLQqUgGwBVlVRC9MBnOXjCh08AczgBeAUJGiAfJLgBvAFBx1CDgC449Ri3YoA3CoC+JlaEixNKOAGUidFnQBewTPZliAssBikigBKwOTQmFSCwmIANNLR4gA+cACuPMDEIh5yFlbQ8Eh2ACIQALYAoiBCFISYGNhFpAGKqhpweFjAhsA6UbImbaX+pDqOwi7unt6ifgEm5iqW4Pm2qPVdECWlY87MbkRU63BcwDz86-iMZJQKUgAUahod2DrrMSoAlJIKOxMeXglZqRcssbIU1p0gb89u4oD5SGBDp0AKp4OgQPDHKqnPjoTqXIjXYC3ZSPdQAbSOIjx2FR6MxpHOnXJAHJwSyALoc16dVibMrQ-ZQJHYHJmCxhPCCOAAC0ZiiorCx3FxEAARgArUIwOQPNoQHSsd5tADWvWmcGSeBSpTVREtAkQtogzHeHx0JrgdH4JuAiAgxDgSokCgA8prtQA6OW8UMAdzwdwgcRNHwlGOlQkQgvcyN4snhYDAsgVSpOZxpuAIhIoxL1GlKCOLYh52ChTj+cIRirkxqenUNbu+pLadEDdxjd0bRdkcWewEj4I+X1abQ0kulgSk0+bonJ88XHA5FjX64z8DVilId3nacWp-UUH8KSgDJvnTvbVM940MBlUAgON2mAIDunKKAAKgO4WVRXgUiLfIPHaTpVhZO9zCAA

πŸ’» Code

export type NodeInterface<LiteralType extends string = string> = {
    type: LiteralType;
};

export type SerializedStringMeta = Record<string, string | undefined>;

export type DomExtractedNodeData = {
    nodeType: string;
    meta: SerializedStringMeta;
};

export type NodeToDomSerializer<Node extends NodeInterface> = (
    node: Node,
) => SerializedStringMeta;

export type NodeMetaSerializerMap<NodeUnion extends NodeInterface> = {
    [Node in NodeUnion as Node['type']]: NodeToDomSerializer<Node>;
};

const has = <T extends object>(
    o: T,
    k: string | number | symbol,
): k is keyof T => Object.hasOwn(o, k);

const trySerializeUsingMapping = <T extends NodeInterface>(
    mapping: NodeMetaSerializerMap<T>,
    node: T,
) => {
    if (has(mapping, node.type)) {
        const a = mapping[node.type];

        const b = a(node);

        return a(node);
    }

    throw new TypeError('Unsupported node type');
};

πŸ™ Actual behavior

Type of the variable a is correctly inferred to be NodeMetaSerializerMap<T>[keyof NodeMetaSerializerMap<T>]. To be rigorous this should be NodeToDomSerializer<T>.

Type of variable b, however, is inferred to be any. Compiler doesn't warn me about this, and as you can understand, this could lead to all sorts of potential any-leakages. Compiler option strict doesn't change this as well.

πŸ™‚ Expected behavior

I expect type of the variable b to be correctly inferred as SerializedStringMeta, or at least some kind of error to be shown in the call site of a(node).

Additional information about the issue

I've also reviewed this pull request. I've tried the following work-around:

type TypeToNodeMapping<Node extends NodeInterface> = {
    [K in Node as Node['type']]: Node;
};

export type NodeMetaSerializerMap<NodeUnion extends NodeInterface> = {
    [K in keyof TypeToNodeMapping<NodeUnion>]: NodeToDomSerializer<
        TypeToNodeMapping<NodeUnion>[K]
    >;
};

This results in a following error being shown at the call site for a(node):

image

This also seems like a weird behaviour, because I've tested different things and verified that TypeToNodeMapping<NodeInterface>[NodeInterface['type']] is correctly resolved as NodeInterface. Seems to me like the compiler is for some reason refusing to unpack the types further, despite the fact it seems safe to use the upper boundary NodeInterface when resolving types for T.

Activity

  1. ahejlsberg commented on Jan 30, 2024

    @ahejlsberg
    Member

    We have bit of a complicated history for our behavior here. We would error on the call to a(node) in TS versions prior to 5.1. In 5.1 we merged #53066 which (erroneously) caused the error to disappear, instead just treating a as an untyped function that takes any parameter list and returns any. That PR was reverted in #57202, so this will become an error again in 5.4 (but isn't an error in the just released 5.4-beta).

  2. ahejlsberg commented on Jan 30, 2024

    @ahejlsberg
    Member

    With the current nightly build, the a variable has type NodeMetaSerializerMap<T>[keyof NodeMetaSerializerMap<T>]. In this particular example we can simplify keyof NodeMetaSerializerMap<T> to T["type"] (but that isn't always the case, as demonstrated by #55129). However, the checker can't further simplify NodeMetaSerializerMap<T>[T["type"]] because it is not sound to conclude that this is the same as NodeToDomSerializer<T>. Specifically, there is no guarantee that T["type"] represents a unique value for every T, so T["type"] could very well be a lossy one-way mapping.

    Another way to look at it is, mapping[node.type] produces a function that is known to be safe to invoke for that node instance. But the checker only tracks T, the type of node, and there could well be other values of type T for which it isn't safe to invoke a.

  3. ahejlsberg commented on Jan 30, 2024

    @ahejlsberg
    Member

    In conclusion, I'm going to close this as fixed by #57202 because we now issue an error as expected.

  4. added
    FixedA PR has been merged for this issue
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Jan 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

FixedA PR has been merged for this issue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions