language-tools: This expression is not callable. Type '{}' has no call signatures - Dynamic slots + Generic

Hi 👋🏻

I was playing with the generic components feature and stumbled upon this issue where I get a type error on the slot like below: image

Reproduction: https://github.com/jd-solanki/volar-vue-playground/blob/generic-component-type/src/bar/Bar.vue

Note Watch out for branch

  System:
    OS: Windows 10 10.0.22621
    CPU: (16) x64 AMD Ryzen 7 3700X 8-Core Processor
    Memory: 6.86 GB / 15.95 GB
  Binaries:
    Node: 18.14.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - C:\Program Files\nodejs\yarn.CMD
    npm: 9.4.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.22621.1555.0), Chromium (112.0.1722.68)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    vue: 3.3.0-beta.4 => 3.3.0-beta.4 
    vue-tsc: ^1.6.4 => 1.6.4

P.S. It will be much easier for you if you add issue template

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 27 (18 by maintainers)

Most upvoted comments

@xiaoxiangmoe

I get an error on vue 3.3.4 🤔 image

import { defineProps, defineSlots } from "vue";

type RowKey<Row extends Record<string, unknown>> = keyof Row & string;
interface ATablePropColumn<Row extends Record<string, unknown>> {
  name: RowKey<Row>;
}

function fakeComponent<Row extends { [K: string]: any }>() {
  const props = defineProps<{
    rows: Row[];
    cols: ATablePropColumn<Row>[];
  }>();

  type aTableSlots<T extends Record<string, unknown>> = {
    [K in keyof T as `header-${K & string}`]: (_: { col: ATablePropColumn<T> }) => any;
  } & {
    ["before-table"]?: (_: { a: string }) => any;
  };

  const slots = defineSlots<aTableSlots<Row>>();

  for (const col of props.cols) {
    const slot = slots[`header-${String(col.name)}`];

    slot?.({ col });
  }
}

fakeComponent();

I would strongly recommend not using aTableSlots since the return type is not used!

I would recommend switching to a type

type aTableSlots<T extends Record<string, unknown>> = {
  [K in keyof T as `header-${K & string}`]: (_: {
    col: ATablePropColumn<T>
  }) => any
} & {
  ['before-table']?: (_: { a: string }) => any
}

The reason being defineSlots is plainly a typescript implementation without any runtime validation.

To use this new aTableSlots is fairly easy by:

const slots = defineSlots<aTableSlots<Row>>()

full example:

<script lang="ts" setup generic="Row extends { [K: string]: any}">
interface ATablePropColumn<Row extends Record<string, unknown>> {
  name: RowKey<Row>
}
const props = defineProps<{
  rows: Row[]
  cols: ATablePropColumn<Row>[]
}>()

type RowKey<Row extends Record<string, unknown>> = keyof Row & string

type aTableSlots<T extends Record<string, unknown>> = {
  [K in keyof T as `header-${K & string}`]: (_: {
    col: ATablePropColumn<T>
  }) => any
} & {
  ['before-table']?: (_: { a: string }) => any
}

const slots = defineSlots<aTableSlots<Row>>()

for (const col of props.cols) {
  const slot = slots[`header-${String(col.name)}`]
  slot?.({ col })
}
</script>

<template>
  <div class="wrapper">
    <div v-for="row in rows">
      <header v-for="(col, index) in cols" :key="index">
        <slot :name="`header-${String(col.name)}`" :col="col"></slot>
        <!--  This expression is not callable. Type '{}' has no call signatures. -->
      </header>
    </div>
  </div>
</template>