Skip to content

['a' | 'b'] expect to receive ['a'] #55225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
crazyair opened this issue Aug 1, 2023 · 12 comments
Closed

['a' | 'b'] expect to receive ['a'] #55225

crazyair opened this issue Aug 1, 2023 · 12 comments

Comments

@crazyair
Copy link

crazyair commented Aug 1, 2023

Bug Report

DeepNamePath<T> result is equal to ['a'] | ['b'], but result different

🔎 Search Terms

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions ______ and _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
  • I was unable to test this on prior versions because _______

⏯ Playground Link

Playground link with relevant code

💻 Code

export type DeepNamePath<T = any> = T extends Record<string, any>
  ? {
    [P in keyof T]: [P] | DeepNamePath<T[P]>;
  }[keyof T]
  : never;

type ddd<T = any> = ['a'] | ['b']  // result is ['a']
// type ddd<T = any> = DeepNamePath<T>;  // result is ['a'] | ['b']


function func<T = any, T1 extends ddd<T> = ddd<T>>(data: T, params: T1) {
  console.log('data', data);
  return params;
}

export const d = func({ a: '', b: '' }, ['a']);
type result = typeof d
//  ^?

🙁 Actual behavior

['a']

🙂 Expected behavior

['a' , 'b']

@MartinJohns
Copy link
Contributor

🙂 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, and params is just typed T1. This type argument is inferred from the provided parameter, and you provide ["a"] as the value. This looks just fine.

@crazyair
Copy link
Author

crazyair commented Aug 1, 2023

🙂 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, and params is just typed T1. 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 data

@crazyair
Copy link
Author

crazyair commented Aug 1, 2023

func({ a: { a1: string }, b: number }, ['a']);

input ['a', 'a1'] result string
input b result number

@crazyair
Copy link
Author

crazyair commented Aug 1, 2023

 export 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 requires DeepNamePath to return an array

@Andarist
Copy link
Contributor

Andarist commented Aug 1, 2023

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 T1 has a tuple-like constraint (getBaseConstraintOfType just returns unknown). 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 const context and has a similar effect on the overall thing.

@crazyair
Copy link
Author

crazyair commented Aug 1, 2023

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 T1 has a tuple-like constraint (getBaseConstraintOfType just returns unknown). 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 const context 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>

@jcalz
Copy link
Contributor

jcalz commented Aug 1, 2023

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! 🙏

@crazyair
Copy link
Author

crazyair commented Aug 2, 2023

@Andarist

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 }],
};

value type is readonly ["a"] | readonly ["b"], i want readonly ["a"]

@crazyair
Copy link
Author

crazyair commented Aug 2, 2023

-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)

@Andarist
Copy link
Contributor

Andarist commented Aug 2, 2023

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

@crazyair
Copy link
Author

crazyair commented Aug 2, 2023

I need

const demo: DemoProps<xx> = xxx

@crazyair
Copy link
Author

crazyair commented Aug 2, 2023

If list there are 2 pieces of data (parameter) value: "a" | "b"
If list there 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 },
  ],
});

@microsoft microsoft locked as resolved and limited conversation to collaborators Aug 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants