Skip to content

bug: generic type argument not inferred from function argument #30975

Description

@jorroll

TypeScript Version: 3.4.3

Search Terms:
generic type argument inference

Code

A reproduction of the error can be found in this StackBlitz example: https://stackblitz.com/edit/typescript-kjji5c.

The stackblitz code is copied below:

import moment from 'moment';

/**
 * The "test()" function below shows a type problem.
 * Ideally, typescript would be able to use the `schedule`
 * argument to `addSchedulePattern()` to infer the 
 * `addSchedulePattern()` type argument `T`. However,
 * typescript fails to do this.
 * 
 * - "test2", below, should throw a type error when passed 
 *   `regularDate`, but it doesn't.
 * 
 * - when I explicitly add typing to `addSchedulePattern()` in
 *   "test4", then typescript correctly catches the type error
 */

function test(
  schedule: Schedule<typeof MomentDateAdapter>, 
  momentDate: moment.Moment, 
  regularDate: Date
) {
  const test = addSchedulePattern(
    momentDate,
    schedule,
  )

  const test2 = addSchedulePattern(
    regularDate, // <- this should error but doesn't
    schedule,
  )

  const test3 = addSchedulePattern<typeof MomentDateAdapter>(
    momentDate,
    schedule,
  )

  const test4 = addSchedulePattern<typeof MomentDateAdapter>(
    regularDate, // <- this properly errors
    schedule,
  )
}

class DateAdapter {
  static readonly date: unknown;
  readonly date: unknown;
}

class MomentDateAdapter extends DateAdapter {
  static readonly date: moment.Moment;
  readonly date: moment.Moment;
}

class Schedule<T extends typeof DateAdapter> { }

function addSchedulePattern<
  T extends typeof DateAdapter,
  R extends Schedule<T> = Schedule<T>,
  >(
    date: T['date'],
    schedule: R
  ): R {
  return schedule;
}

Expected behavior:

It is expected that addSchedulePattern() can infer the type argument T from the provided schedule argument of type R extends Schedule<T>.

Actual behavior:

addSchedulePattern() always resolves the type argument T as typeof DateAdapter

Playground Link:

https://stackblitz.com/edit/typescript-kjji5c

Related Issues:

#30505 may be related

Activity

  1. Dudeonyx commented on Apr 17, 2019

    @Dudeonyx

    try removing the default type param for R. Sometimes setting defaults can interfere with inferrence

  2. jorroll commented on Apr 17, 2019

    @jorroll
    Author

    Umeayo Onyekachukwu (@Dudeonyx) good idea, but it doesn't make a difference. You can test by opening up the stackblitz example, making the change, and then hovering over addSchedulePattern in test2 to see how typescript has typed addSchedulePattern().

    I'm not going to update the stackblitz example with this change because it would force me to explicitly type the date argument in test4, eliminating the error typescript shows. This would make it harder to "see" the problem.

  3. RyanCavanaugh commented on Apr 17, 2019

    @RyanCavanaugh
    Member

    The problem is effectively here:

    function addSchedulePattern<
      T extends typeof DateAdapter,
      R extends Schedule<T> = Schedule<T>,
      >(
        date: T['date'],
        schedule: R
      ): R {
      return schedule;
    }

    You don't really have an inference site for T here, which is very problematic - TS is going to be falling back to the constraint for the type parameter any time it isn't explicitly specified. Compounding that is the fact that you have two type parameters but really only one floating type in play, so the inferences being made are 100% valid.

    What you want is this definition:

    type DateFor<S> = S extends Schedule<infer R> ? R["date"] : never;
    function addSchedulePattern<S extends Schedule<typeof DateAdapter>>(date: DateFor<S>, schedule: S): S {
      return schedule;
    }

    which properly errors in both cases.

  4. jorroll commented on Apr 17, 2019

    @jorroll
    Author

    Ryan Cavanaugh (@RyanCavanaugh) thanks! That's very helpful to know. This example was simplified (perhaps too much so) and trying out your suggestion creates two new problems:

    Assuming these types:

    type DateInput<T extends typeof DateAdapter> = T['date'] | InstanceType<T> | DateTime;
    
    interface ITypedWithDateAdapter<T extends typeof DateAdapter> {}
    
    type DateAdapterFor<O> = O extends ITypedWithDateAdapter<infer A> ? A : never;

    1) The following throws the error Type 'Schedule<typeof DateAdapter>' is not assignable to type 'S'

    This is obviously strange because it's saying Schedule<typeof DateAdapter> is not assignable to S extends Schedule<typeof DateAdapter>

    function addSchedulePattern<S extends Schedule<typeof DateAdapter>>(
      pattern: Pattern, 
      date: DateInput<DateAdapterFor<S>>, 
      schedule: S, 
      options: { dateAdapter?: DateAdapterFor<S> } = {}
    ): S {
        // `Schedule<T>#add(): Schedule<T>`
        return schedule.add('rdate', date); // this throws the error
    }

    I can easily work around this error with return schedule.add('rdate', date) as S

    2) The following also throws an error for schedule.add()

    function addSchedulePattern<S extends Schedule<typeof DateAdapter>>(
      pattern: Pattern, 
      date: DateInput<DateAdapterFor<S>>, 
      schedule: S, 
      options: { dateAdapter?: DateAdapterFor<S> } = {}
    ): S {
      // Where `Schedule<T>#add(type: 'rrule', value: Rule<T>): Schedule<T>`
      return schedule.add(
        'rrule',
        new Rule(buildRecurrencePattern(pattern, date, options), options), // <- PROBLEM IS HERE
      );
    }

    Basically:

    • In the function definition, schedule is being typed as Schedule<typeof DateAdapter>.
    • Schedule<T>#add() is expecting the second argument to be Rule<T>, so it is expecting Rule<typeof DateAdapter>
    • However, new Rule() is being typed correctly as Rule<DateAdapterFor<S>>
    • The fact that Rule<DateAdapterFor<S>> is not Rule<typeof DateAdapter> seems to be causing an error.

    I doubt you'll want to look at it, but FYI the original code for these examples is here: https://gitlab.com/john.carroll.p/rschedule/blob/master/packages/rule-tools/src/lib/schedule.ts#L53-66

    Update

    I can work around error (2) by asserting schedule as Schedule<DateAdapterFor<S>>, but then I run into (1) again.

  5. jorroll commented on Apr 17, 2019

    @jorroll
    Author

    Perhaps these errors are different issues?

  6. RyanCavanaugh commented on Apr 18, 2019

    @RyanCavanaugh
    Member

    This is obviously strange because it's saying Schedule<typeof DateAdapter> is not assignable to S extends Schedule<typeof DateAdapter>

    This is a correct error; see https://stackoverflow.com/questions/46980763/why-cant-i-return-a-generic-t-to-satisfy-a-partialt for a similar example. In general for a T extends U, you cannot assign a U to a T because T may be instantiated with a more-specific type at runtime.

    I don't see anything here that looks like a TypeScript bug.

  7. jorroll commented on Apr 19, 2019

    @jorroll
    Author

    Interesting. Thanks for the information!

    I don't see anything here that looks like a TypeScript bug.

    Gotcha

  8. locked as resolved and limited conversation to collaborators on Oct 21, 2025
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

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions