Search code examples
typescripttypescript-typingstypescript-genericsfunction-parameter

TypeScript: Generic types for functions treat parameters as "any" despite declaring them otherwise


Using TypeScript 4.4.3, I want to be able to explicitly type function parameters to a function that returns a generic. TypeScript is ignoring the types for the parameters to functions that use generics treating them as any.

The following is a contrived example (not a very useful function) that will show a warning on the input parameter saying it's of type "any" if it's pasted into the TS Playground...

type GenericResult = <T>(input: number) => T
export const returnWhatever: GenericResult = <T>(input) => <T> input

Is the parameter's type defined in the first line supposed to be ignored?

My actual use-case looks like this...

import type { QueryResult } from 'pg'
import pg from 'pg'

const pgNativePool = new pg.native.Pool({
  max: 10,
  connectionString: import.meta.env.VITE_DATABASE_URL,
  ssl: {
    rejectUnauthorized: false
  }
})

type AcceptableParams = number | string | boolean

type PostgresQueryResult = (sql: string, params?: AcceptableParams[]) => Promise<QueryResult<any>>
const query: PostgresQueryResult = (sql, params?) => pgNativePool.query(sql, params)

type GetOneResult = <T>(sql: string, id: number | string) => Promise<T>
const getOne: GetOneResult = async <T>(sql, id) => {
  const { rows } = await query(sql, [id])
  return <T> rows[0]
}

const user = await getOne<User>('SELECT * FROM users WHERE id = $1;', 33)
// returns a User

In this example above, the sql parameter must always be a string and the id can be a number or a string.

While the type of the return value for the function is determined where the function is called, it still seemed like a good idea to be able to type the parameters to that function since that part is known.

Possible?


Solution

  • The recommendation from Microsoft is to explicitly type the parameters in the generic function's definition as I have done below with the getOne function below. This doesn't "feel" right because I'm defining the parameter types twice but whatever...

    import type { QueryResult } from 'pg'
    import pg from 'pg'
    
    const pgNativePool = new pg.native.Pool({
      max: 10,
      connectionString: import.meta.env.VITE_DATABASE_URL,
      ssl: {
        rejectUnauthorized: false
      }
    })
    
    type AcceptableParams = number | string | boolean
    
    type PostgresQueryResult = (sql: string, params?: AcceptableParams[]) => Promise<QueryResult<any>>
    const query: PostgresQueryResult = (sql, params?) => pgNativePool.query(sql, params)
    
    type GetOneResult = <T>(sql: string, id: number | string) => Promise<T>
    const getOne: GetOneResult = async <T>(sql: string, id: number | string) => {
      const { rows } = await query(sql, [id])
      return <T> rows[0]
    }
    
    const user = await getOne<User>('SELECT * FROM users WHERE id = $1;', 33)
    // returns a User