Repository navigation
['a' | 'b'] expect to receive ['a'] #55225
Description
Activity
MartinJohns commented
on Aug 1, 2023 ContributorMore actions🙂 Expected behavior
['a' , 'b']Why do you expect this behaviour? What's your reasoning and thought process?
The return argument of your function is just
params, andparamsis just typedT1. This type argument is inferred from the provided parameter, and you provide["a"]as the value. This looks just fine.🙂 Expected behavior
['a' , 'b']Why do you expect this behaviour? What's your reasoning and thought process?
The return argument of your function is just
params, andparamsis just typedT1. This type argument is inferred from the provided parameter, and you provide["a"]as the value. This looks just fine.I want result is
['a'], will be used to obtain the value of datafunc({ a: { a1: string }, b: number }, ['a']);input
['a', 'a1']resultstring
inputbresultnumberexport type DeepNamePath<T = any> = T extends Record<string, any> ? { - [P in keyof T]: [P] | DeepNamePath<T[P]>; + [P in keyof T]: [P] | [...DeepNamePath<T[P]>]; }[keyof T] : never;If I write this way, I can get
['a'], but it requiresDeepNamePathto return an arrayYour second argument gets widened to
string[]in the first inference pass. That's gathered as the inference candidate, it fails the constraint check so the constraint gets selected as the inference result.TS doesn't recognize that your
T1has a tuple-like constraint (getBaseConstraintOfTypejust returnsunknown). I suspect that's because it's a recursive type without a direct hint that it returns a tuple type. When u wrapped the second union member with[...T]you essentially gave it that hint and that's why it works.For similar reasons this one work:
export type DeepNamePath<T = any> = T extends Record<string, any> ? { [P in keyof T]: readonly [P] | DeepNamePath<T[P]>; }[keyof T] : never; type ddd<T = any> = DeepNamePath<T>; function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) { console.log('data', data); return params; } export const d = func({ a: '', b: '' }, ['a']);
This one doesn't really recognize the tuple-like base constraint but it introduces a
constcontext and has a similar effect on the overall thing.Reacted by 叶枫Your second argument gets widened to
string[]in the first inference pass. That's gathered as the inference candidate, it fails the constraint check so the constraint gets selected as the inference result.TS doesn't recognize that your
T1has a tuple-like constraint (getBaseConstraintOfTypejust returnsunknown). I suspect that's because it's a recursive type without a direct hint that it returns a tuple type. When u wrapped the second union member with[...T]you essentially gave it that hint and that's why it works.For similar reasons this one work:
export type DeepNamePath<T = any> = T extends Record<string, any> ? { [P in keyof T]: readonly [P] | DeepNamePath<T[P]>; }[keyof T] : never; type ddd<T = any> = DeepNamePath<T>; function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) { console.log('data', data); return params; } export const d = func({ a: '', b: '' }, ['a']);
This one doesn't really recognize the tuple-like base constraint but it introduces a
constcontext and has a similar effect on the overall thing.I thought of const, but I couldn't write it at the time.
const T1 extends ddd<T> = ddd<T>Since this hasn't been said explicitly yet: this isn't a bug in TypeScript so this isn't really the right place for this discussion. Stack Overflow or the TS Discord would be more appropriate venues. 叶枫 (@crazyair), could you please close the issue to free up the TS team's time? Once it's closed I imagine anyone who's still interested could continue discussing here.
edit: Thanks! 🙏
export type DeepNamePath<T = any> = T extends Record<string, any> ? { [P in keyof T]: readonly [P] | DeepNamePath<T[P]>; }[keyof T] : never; type ddd<T = any> = DeepNamePath<T>; function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) { console.log('data', data); return params; } // export const d = func({ a: '', b: '' }, ['a']); export interface ColumnType< RecordType = any, T1 extends DeepNamePath<RecordType> = DeepNamePath<RecordType>, > { dataIndex?: T1; render?: (value: T1) => any; } export interface DemoProps<T = any> { data: readonly T[]; columns: ColumnType<T, DeepNamePath<T>>[]; } export const result: DemoProps<{ a: number; b: string }> = { data: [{ a: 1, b: '' }], columns: [{ dataIndex: ['a'], render: value => value }], };
valuetype isreadonly ["a"] | readonly ["b"], i wantreadonly ["a"]-export interface ColumnType<RecordType = any, T1 extends DeepNamePath<RecordType> = any> { +export interface ColumnType<RecordType = any, const T1 extends DeepNamePath<RecordType> = any> { dataIndex?: T1; render?: (value: T1) => any; }
'const' modifier can only appear on a type parameter of a function, method or classts(1277)To make it work you need to be in some inference context and that means that you need a function call to make it work (and annotate its type params as
const). This kinda should work:export type DeepNamePath<T = any> = T extends Record<string, any> ? { [P in keyof T]: readonly [P] | readonly [...DeepNamePath<T[P]>]; }[keyof T] : never; declare function make<T, const T2 extends readonly DeepNamePath<T>[]>(arg: { data: T[]; columns: { [K in keyof T2]: { dataIndex: T2[K]; render: (value: T2[K]) => void; }; }; }): void; export const result = make({ data: [{ a: 1, b: "" }], columns: [{ dataIndex: ["a"], render: (value) => value }], });
but it doesn't because TS doesn't currently handle context-sensitive functions in reverse-mapped types that well. It's something I want to improve in #54029
Reacted by 叶枫I need
const demo: DemoProps<xx> = xxx
If
listthere are 2 pieces of data(parameter) value: "a" | "b"
Iflistthere are 1 pieces of data(parameter) value: "a"declare function func<TData = any, const T1 extends keyof TData = any>(data: { data: TData[]; list: { key?: T1; render?: (value: T1) => any; }[]; }): any; func({ data: [{ a: 1, b: '2' }], list: [ { key: 'a', render: value => value }, { key: 'b', render: value => value }, ], });
- locked as resolved and limited conversation to collaborators
on Aug 2, 2023
Bug Report
DeepNamePath<T>result is equal to['a'] | ['b'], but result different🔎 Search Terms
🕗 Version & Regression Information
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
['a']🙂 Expected behavior
['a' , 'b']