Skip to content
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

Type predicate doesn't narrow down function overload (but assertion works) #58660

Closed
eachirei opened this issue May 25, 2024 · 8 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@eachirei
Copy link

eachirei commented May 25, 2024

πŸ”Ž Search Terms

type predicate, function overload, discriminating unions

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about method overload, unions

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.4.5#code/C4TwDgpgBAgg+gFSgXigAxgEgN4GdgBOAlgHYDmAvmgNwBQokUAQoiukzvseVXfeNHgkA9iTgAbIsAgEAhuLYByGAGUAIoroNoLEWMnS5C1IqbrNtWgGNR+KNoAKBCABMiV2dKGiJUmfLYACgA3eQAuKFkSEABKCNCFIlxYOD1fQwDkAD4oBIA6fFkCYFwAdSkAC0DlRRi6GxI7R2c3D2ldHwN-YygQ8MjouNyApOZUzr8jFBz8wuKyyuqYWr4AMwBXEitgIlEoVZJm13dPCG99Sfk+8Qio2PiR5PP07qhsKGdgdYISYfEC4BFErlYBVGoxChrTbbXa-WS4XAyYAASVwzy6Rmut0GtwRSOSCSgo3RlwU2FoUCJq16AEIjq1TjBrjEYm8KZT7BUCMIAO5QEgQPkAUQI3IIgQARAA5YTASJQLikMg0iV1dkUWga6y2OX0k5eIIJbH3P5Ep6sbJ-AFAhagpYrbWNXUCJzHNoQJiG-p3IaE0YsJCW2aA+YgsFMB20DZbHZ7A5691Mo0DE1+81Id6fb6-YM2sP2upa6MwvbwxHFVFJ704yJ4+am4mscmUojUwJ0l0tfVnZms5sc0HcvkC4Wi4Ti6Wy+WK8gqtWUjVF6GxuF1lG4JhYlNDMv4hvJANslttjuQV0M9q9o8Drm8-mCqAisWSmVy2QKwhKud0BeayzFlcoCsZxGXHTdZAieAEAAGigMAAAYIhIdYAFsACMZCGZD0JkKNl1hICQK8MDAjQiIA1ghCIhnMgsNQjCCDwmMCOAiBQIITc8i4gBtWQAHkOMo+CAF0Il4yDECEpD6JkYSoAAHygbiyLGGC4MQj9uDIOSoDonCCDZdlW0CBNGUCfiOJZftKQaXBhHECA8nEYQyECABZTwKgKABHYpAgQlkfygDVj0CeNOzdMyLKYKz2Rs2x7Mc5zXI80EfL8gL52Cyx2V3Cs0XMgSYqC2zEqclz3M89LgH8+DApyyksx+KB4LoJdmL2VjTgAJhgEiIJSNIMXkKT+Rkgg9IYpiS1+LrpF6kiVI6C4MnEUaaMm3CAJYoiIAWjjAi4vJeKKoTRKUgaSVW0bsIYuTFOU8jxhW7p1s-cgdM2gzySM1YTIii8zmel5MWi2KOSAhKHPKlKqtwXyasyoKQqpMLDgB7sru6QrLJiazIcaMrksqtL4Yyuqsq1XK10rYHhvEHHivZUroeJ1KvLJxGKb4dkmt+VrNSAA

πŸ’» Code

type A_T = `A${string}`;
type B_T = `B${string}`;

type A_non_literal = 'ASD';
type B_non_literal = 'BSD';

const typePredicateA_non_literal = (val: any): val is A_non_literal => val.startsWith('A');
const typePredicateB_non_literal = (val: any): val is B_non_literal => val.startsWith('A');

function fntypePredicateA_non_literal(val: any): val is A_non_literal { return val.startsWith('A')};

function assertIsA_non_literal(val: any): asserts val is A_non_literal {
  if (!typePredicateA(val)) {
    throw new Error("Not a string!");
  }
}

const typePredicateA = (val: any): val is A_T => val.startsWith('A');
const typePredicateB = (val: any): val is B_T => val.startsWith('B');

function fntypePredicateA(val: any): val is A_T { return val.startsWith('A');}

function assertIsA(val: any): asserts val is A_T {
  if (!typePredicateA(val)) {
    throw new Error("Not a string!");
  }
}

function assertIsB(val: any): asserts val is B_T {
  if (!typePredicateB(val)) {
    throw new Error("Not a string!");
  }
}

function createAorB(a: A_T, p0: number): number
function createAorB(b: B_T, p0: string): number
function createAorB(...[aOrB, p0]: [a: A_T, p0: number] | [b: B_T, p0: string]  ): number {

  if(typePredicateA(aOrB)){
    console.log(Math.sqrt(p0)); // this throws compilation error
  }
  if(fntypePredicateA(aOrB)){
    console.log(Math.sqrt(p0)); // this throws compilation error
  }


  assertIsA(aOrB);
  console.log(Math.sqrt(p0));


  return 0;
}

function create2AorB(a: A_non_literal, p0: number): number
function create2AorB(b: B_non_literal, p0: string): number
function create2AorB(...[aOrB, p0]: [a: A_non_literal, p0: number] | [b: B_non_literal, p0: string]  ): number {

  if(typePredicateA_non_literal(aOrB)){
    console.log(Math.sqrt(p0)); // this throws compilation error
  }
  if(fntypePredicateA_non_literal(aOrB)){
    console.log(Math.sqrt(p0)); // this throws compilation error
  }


  assertIsA_non_literal(aOrB);
  console.log(Math.sqrt(p0));


  return 0;
}

πŸ™ Actual behavior

After the type predicate, the second parameter isn't narrowed down to number, hence the error:

Argument of type 'string | number' is not assignable to parameter of type 'number'.
  Type 'string' is not assignable to type 'number'.

After the assertion, the code works as expected.

πŸ™‚ Expected behavior

Type predicate to do the same narrowing as type assertion.

Additional information about the issue

No response

@jcalz
Copy link
Contributor

jcalz commented May 26, 2024

I don't see what this has to do with overloads, per se... you could remove those call signatures and the problem persists.

Looks like #46266 (comment) alludes to this... type predicates don't seem to work to discriminate destructured discriminated unions. Not sure if there's another issue open for it somewhere.

@eachirei
Copy link
Author

hey @jcalz! you're correct, I just assumed that maybe there's some special logic that's used for choosing the overload. What I find surprising is that type assertion works. Would it be possible to replicate this behavior for type predicates?

@fatcerberus
Copy link

I just assumed that maybe there's some special logic that's used for choosing the overload

Overloads only matter for the caller. The type checker doesn't care about any overloads when checking the function body (which is why you need a separate implementation signature); what narrowing you do see here, with the assertion function, is just normal discriminated union narrowing. If you were to declare the implementation as, e.g. (ab: string, p0: string | number) then you wouldn't have a discriminated union anymore and would no longer be able to narrow p0 based on ab at all.

Anyway, it's likely just a bug that the assertion works but the type predicate doesn't.

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Jun 6, 2024
@RyanCavanaugh
Copy link
Member

Please provide a minimal repro; this doesn't sound based on the description like it needs 70 lines to demonstrate

@fuxichen
Copy link

fuxichen commented Jun 7, 2024

Please provide a minimal repro; this doesn't sound based on the description like it needs 70 lines to demonstrate

Why can't I deduce the type of data??
Please help to see why the type of data cannot be inferred,
As shown in the following demo:
Function overload has restricted the input type, if type=TestType When A, the data must be TypeAData.
I think type=TestType was determined through the switch At time A, it should be inferred that data=TypeAData

https://www.typescriptlang.org/play/?ts=5.4.5#code/KYOwrgtgBAKsDOAXGBPADsKBvAUFKAglALxQDkAhmQDR5QBCJ59NOAvjjgJYiLABOAMwoBjTKgwEAIhUQVsdROmAAuWAmTKAdAVr4A9PqhaTW9px58ho8cvoy5C-Eoxq4SCcC309UQ8ZNzHEEwEBFELgB7ECgXYAIAMVCACgATWQo3ZWkMgEonPyNTcxCwiOjYuySQNIysjHs8gv9ijmDQ8KiYuOrkuLcNTx1qKHS5eviHCly1ADdIrlT2sq7KjF7+9Q9tH1G62Dspmah5xeXOip6UzfdNDBGxzIPJKagAH2fgRrlj09SC+AAdy4iBEAAsoH1lPlcPh8CIKPBxINtAQVHQ4Wt4r1HrkANyFKAAdTBKCgCJAZEQUAAkqNgKkwGJYmDMHEoJFBHs5AB+HkYuEAI34wAoAGs8QKEUitncvPR0ZjnFUUo8oIjPt9ppKlVBhaKJQLUsBhGAADaIRVQDhsIA

@fuxichen
Copy link

fuxichen commented Jun 7, 2024

Please provide a minimal repro; this doesn't sound based on the description like it needs 70 lines to demonstrate

Please help to see why the type of data cannot be inferred,
As shown in the following demo:
Function overload has restricted the input type, if type=TestType When A the data must be TypeAData.
I think type=TestType was determined through the switch At time A, it should be inferred that data=TypeAData

https://www.typescriptlang.org/play/?ts=5.4.5#code/KYOwrgtgBAKsDOAXGBPADsKBvAUFKAglALxQDkAhmQDR5QBCJ59NOAvjjgJYiLABOAMwoBjTKgwEAIhUQVsdROmAAuWAmTKAdAVr4A9PqhaTW9px58ho8cvoy5C-Eoxq4SCcC309UQ8ZNzHEEwEBFELgB7ECgXYAIAMVCACgATWQo3ZWkMgEonPyNTcxCwiOjYuySQNIysjHs8gv9ijmDQ8KiYuOrkuLcNTx1qKHS5eviHCly1ADdIrlT2sq7KjF7+9Q9tH1G62Dspmah5xeXOip6UzfdNDBGxzIPJKagAH2fgRrlj09SC+AAdy4iBEAAsoH1lPlcPh8CIKPBxINtAQVHQ4Wt4r1HrkANyFKAAdTBKCgCJAZEQUAAkqNgKkwGJYmDMHEoJFBHs5AB+HkYuEAI34wAoAGs8QKEUitncvPR0ZjnFUUo8oIjPt9ppKlVBhaKJQLUsBhGAADaIRVQDhsIA

@RyanCavanaugh
Copy link
Member

Function overloads don't have any effects on narrowing. For this case you'd have to write something like

function typeFun(...args: [TestType.A, TypeAData] | [TestType.B, TypeBData]): void {
  const [type, data] = args;
  switch (type) {
    case TestType.A:
      typeAFun(data); // Why can't I deduce the type of data??
      break;
    case TestType.B:
      typeBFun(data as TypeBData);
      break;
    default:
  }
}

@RyanCavanaugh RyanCavanaugh added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Needs More Info The issue still hasn't been fully clarified labels Jun 13, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

6 participants