Skip to content

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

Closed as not planned

Description

πŸ”Ž 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

Activity

  1. jcalz commented on May 26, 2024

    @jcalz
    Contributor

    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.

  2. eachirei commented on May 27, 2024

    @eachirei
    Author

    hey Joe Calzaretta (@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?

  3. fatcerberus commented on May 27, 2024

    @fatcerberus

    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.

  4. RyanCavanaugh commented on Jun 6, 2024

    @RyanCavanaugh
    Member

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

  5. fuxichen commented on Jun 7, 2024

    @fuxichen

    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

  6. fuxichen commented on Jun 7, 2024

    @fuxichen

    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

  7. RyanCavanaugh commented on Jun 13, 2024

    @RyanCavanaugh
    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:
      }
    }
  8. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Jun 13, 2024
  9. typescript-bot commented on Jun 16, 2024

    @typescript-bot
    Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions