Skip to content

TypeScript 4.6 regression #48118

Description

Bug Report

I have read the release notes and the code that is breaking for me is not mentioned in the breaking changes section.

🔎 Search Terms

None. As the code works with 4.5

🕗 Version & Regression Information

The typeof var === 'function' call narrows the variable to function vs one of the union.

  • This changed between versions 4.5 and 4.6

⏯ Playground Link

Playground link with relevant code

💻 Code

type Context = Record<string, any>

class Foo<T extends Context> {  
  private contextAccumlator!: () => T | Promise<T>

  constructor(
    context: T | (() => T | Promise<T>)
  ) {
    if (typeof context === 'function') {
      this.contextAccumlator = context
    }
  }
}

🙁 Actual behavior

I expect this.contextAccumlator = context to report zero errors.

🙂 Expected behavior

Instead it says

Type '(() => T | Promise<T>) | (T & Function)' is not assignable to type '() => T | Promise<T>'.
  Type 'T & Function' is not assignable to type '() => T | Promise<T>'.
    Type 'Context & Function' provides no match for the signature '(): T | Promise<T>'.(2322)

Activity

  1. tristan00b commented on Mar 4, 2022

    @tristan00b

    Harminder Virk (@thetutlage) I played around with this a bit and reduced your example:

    function isFunction<T extends Function>(it: unknown): it is T
    {
      return typeof it === 'function'
    }
    
    function isFn(it: unknown): it is Function
    {
      return typeof it === 'function'
    }
    
    function f<T>(arg: T | (() => T))
    {
      let x: () => T
     
      if (typeof arg === 'function')
        x = arg // (error) arg: (() => T) | (T & Function)
    
      if (isFunction(arg))
        x = arg // (works) arg: () => T
    
      if (isFn(arg))
        x = arg // (works) arg: () => T
    }

    This can be reproduced as far back as version 3.3.3 (play).

  2. thetutlage commented on Mar 4, 2022

    @thetutlage
    Author

    🤔 Did you mean there is no regression?

  3. jihndai commented on Mar 4, 2022

    @jihndai
    Contributor

    Weirdly enough, it seems those are two different cases. This issue's sample code was indeed working by 4.5.5, broken in 4.6.2, and still broken in 4.7.0-dev.20220304. While the second example was never fixed as far as I can tell.

  4. tristan00b commented on Mar 4, 2022

    @tristan00b

    Maybe a recent change caused the old bug to surface in Harminder Virk (@thetutlage) 's code.

  5. RyanCavanaugh commented on Mar 4, 2022

    @RyanCavanaugh
    Member

    This is a correct error that was not properly found before:

    type Context = Record<string, any>
    
    class Foo<T extends Context> {  
      private contextAccumlator!: () => (T | Promise<T>)
    
      constructor(context: T | (() => T | Promise<T>)
      ) {
        if (typeof context === 'function') {
          this.contextAccumlator = context
        }
      }
    
      invoke() {
        this.contextAccumlator();
      }
    }
    
    function isPromise(arg: any): arg is Promise<any> {
      return false;
    }
    
    const p = new Foo<(n: string) => void>(n => n.substring(0));
    // Crashes
    p.invoke();
  6. thetutlage commented on Mar 5, 2022

    @thetutlage
    Author

    So shouldn't this be under breaking changes?

  7. RyanCavanaugh commented on Mar 5, 2022

    @RyanCavanaugh
    Member

    Yes, that's why it's a Docs bug assigned to the blog post author

  8. thetutlage commented on Mar 5, 2022

    @thetutlage
    Author

    Okay! Thanks for the clarification :)

  9. jakebailey commented on Apr 16, 2024

    @jakebailey
    Member

    This was "fixed" in #49119; should it have been?

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

Metadata

Metadata

Labels

DocsThe issue relates to how you learn TypeScript

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions