UPDATE: The issue is a limitation of TypeScript, the issue is on Typescripts GitHub now: https://github.com/microsoft/TypeScript/issues/47440

I currently have the following code:

class Table<T,> {
   constructor(records: T[], columns: Column<T>[]) { ... }
}

class Column<T> {
   constructor(name: string, transformer: (item: T) => string) { ... }
   addTooltip(transformer: (item: T) => string): this { ... }
}

class Building {
   constructor (public name: string) {}
}

const buildings = [
   new Building("test"),
   new Building("station")
];

I want to create a table, without having to specify that T is of type Building.

This works so far, because typescript can infer the type from the buildings array and it works. Even in the transformer of the Column, I still get type suggestions.

const table = new Table(buildings, [
    new Column("Name", building => building.name)
]);

But when I use addTooltip, typescript can't figure out what T is and completely freaks out, because the Column assumes that T is unknown.

const table = new Table(buildings, [
    new Column("Name", building => building.name),
    new Column("Name", building => building.name).addTooltip(t => t.name)
]);

Is there a way to tell TypeScript to use the type of its records constructor parameter as a source for T, ignoring what the columns think T is - without using new Table<Building>(...)? I know that we could use columns: Column<any>[], but then the type checking in the Columns would not work.

Playground link


I can't tell you how to do what you actually want to do (and what I'd actually want to do), but I can give you a couple of options for workarounds.

  1. You could have a static method on Column that does the combined operation of creating the Column and adding the tooltip.
  2. You could have a static method on Column that accepts a Column and adds a tooltip to it.
  3. You could have a standalone function that does what the static method in #2 does.

When you do that, the inference works.

#1

static withTooltip<T>(
    name: string,
    transformer: (item: T) => string,
    tooltipTransformer: (item: T) => string,
): Column<T> {
    const col = new Column(name, transformer);
    col.addTooltip(tooltipTransformer);
    return col;
}

Then building the array is:

const table = new Table(buildings, [
    new Column("Name", building => building.name),
    Column.withTooltip("Name", building => building.name, t => t.name),
]);

#2

static plusTooltip<T>(col: Column<T>, transformer: (item: T) => string): Column<T> {
    col.addTooltip(transformer);
    return col;
}

Then building the array is:

const table = new Table(buildings, [
    new Column("Name", building => building.name),
    Column.plusTooltip(new Column("Name", building => building.name), t => t.name),
]);

#3

...is just #2 but as a function rather than static method.

Playground link