Skip to content

Unsafe array mutation allowed by loose generic constraints in item-creator functionsΒ #62666

@SimonSimCity

Description

@SimonSimCity

πŸ”Ž Search Terms

mutate array, covariance, covariant, invariance, invariant

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about covariance and invariance

⏯ Playground Link

https://www.typescriptlang.org/play/?target=10&jsx=0&ts=5.9.3#code/MYewdgzgLgBMBOBTAhlRBJNBbGBeAUDDADyFEwAiMiAHmmACYQwDeZ5MAlgwFwzTxOYAOYBudjAC+AGgkAVanUSNmbDkU7YIfCgG0AuuI4yyAPgAUEhCjQA5RAHdMiLH3MBKPKcqyin3KZk5sKCvDBy7nwKAawSSFAArvBgseowAHSZIdy+6pou2jC6menZDOn5WBDScEioiPZO2B76uUSSRlLi+FAAngAOiDAA4qF4qVxafGrk3HwCQmISYMhYiPNQgiKdkgbiHfj4oJCw1vXOWADyYKPc4x5u5tD1fLcM-t5vH2RnaBfmHi8MHMMw0YQA5GBHAB9bjg3KSdzucRAA

πŸ’» Code

I have a grid on which I want to create items by using a helper-function. This function should return a new version of the grid.

The type of the grid is as following:

type Grid = {
  items: {
    id: string;
    name: string;
  }[];
};

In the create-item function, I thought generics would be a cool idea, so I went for this definition:

const createItem =
  <
    D extends {
      id: string;
    },
    T extends {
      items: D[];
    },
  >(
    createNewItem: () => D,
  ) =>
  (grid: T): T => {
    return {
      ...grid,
      items: [...grid.items, createNewItem()],
    };
  };

But when invoking this function, I found a serious flaw in my types:

const createItemOnGrid = (): ((grid: Grid) => Grid) =>
  createItem(() => ({
    id: 'new_id',
  }));

πŸ™ Actual behavior

No error, but data that does not comply with the type when running the code.

πŸ™‚ Expected behavior

An error at compile time.

What gave me the expected result was to rewrite the function as following:

const createItem =
  <
    D extends {
      id: string;
    },
    T extends {
      items: D[];
    },
  >(
    createNewItem: () => T["items"][number],
  ) =>
  (grid: T): T => {
    return {
      ...grid,
      items: [...grid.items, createNewItem()],
    };
  };

Note that I only changed the definition of createNewItem here.

But with the code provided first, I would like to get an error when adding the element to the array, because the types could be incompatible (as shown).

Additional information about the issue

Someone in discord pointed me to the terms "covariance" and "invariance", and I started reading https://stackoverflow.com/questions/8481301/covariance-invariance-and-contravariance-explained-in-plain-english.

When searching for something in this direction with regards to TypeScript, I came to https://stackoverflow.com/questions/60905518/why-are-typescript-arrays-covariant and further via #1394 to #48240, but I couldn't find a way of getting a helpful result.

I also saw this in the documentation https://www.typescriptlang.org/docs/handbook/2/generics.html#variance-annotations but couldn't see why my approach still should not error out.

Discord thread, where I raised this issue first: https://discord.com/channels/508357248330760243/1430830666865709096

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