TypeScript Version: 2.4.0
Code
// A *self-contained* demonstration of the problem follows...
interface SQLQuery<TResult> {
__result: TResult;
}
declare function sql(
literals: ['SELECT id FROM users'],
...placeholders: any[]
): SQLQuery<{id: number}>;
declare function querySync<TResult>(q: SQLQuery<TResult>): Array<TResult>;
const values: Array<{id: number}> = querySync(sql`SELECT id FROM users`);
Expected behavior:
Typescript should see that the input string 'SELECT id FROM users' matches the expected literals of ['SELECT id FROM users'] and use the declared function, allowing me to generate an overloaded version of the sql function for each query.
Actual behavior:
I get the error:
src/index.ts(23,50): error TS2345: Argument of type 'TemplateStringsArray' is not assignable to parameter of type '["SELECT id FROM users"]'.
Property '0' is missing in type 'TemplateStringsArray'.
I think this is contingent on #16592.
Actually, I don't know if this will work even with the changes I have in mind. TemplateStringsArray is effectively a ReadonlyArray<string> - but you can't assign a ReadonlyArray to a mutable Array. So at the minimum you'd have to write something like
interface MyCustomType extends TemplateStringsArray {
0: "SELECT id FROM users"
}
That would be fine. I'm going to be auto-generating these definitions anyway, by parsing the code to see which functions are called. This would be very much a workaround for #16551.
At the moment, that doesn't seem to work either though.
declare function id<T extends string>(list: Array<T>): Array<T>;
declare function readonlyId<T extends string>(list: ReadonlyArray<T>): ReadonlyArray<T>;
type LiteralType = "foo" | "bar";
const array: Array<LiteralType> = [];
const readonlyArray: ReadonlyArray<LiteralType> = [];
const foo: ReadonlyArray<LiteralType> = id(array);
const bar: ReadonlyArray<LiteralType> = readonlyId(array);
// ~~~
// Type 'ReadonlyArray<string>' is not assignable to type 'ReadonlyArray<LiteralType>'.
// Type 'string' is not assignable to type 'LiteralType'.const bar: ReadonlyArray<LiteralType>
const baz: ReadonlyArray<LiteralType> = readonlyId(readonlyArray);
Isn't it a bug that generic type information is being lost when ReadonlyArray is assigned to Array?
@r00ger that looks like a separate bug, it should probably have its own issue.
@DanielRosenwasser can you give an update on next steps here?
There shouldn't be a type TemplateStringsArray, it should be a const string tuple type, so we can write code:
interface SQL<TSA, VS> {
texts: TSA;
values: VS;
}
function sql<TSA extends readonly string[], VS extends any[]>(texts:TSA, ...values: VS): SQL<TSA, VS> {
return { texts, values };
}
// then
let s: SQL<['select * from person where a=', ' and b=', ''], [number, Date]> = sql`select * from person where a=${1} and b=${new Date()}`;