Skip to content

Generic type no longer narrowed as expected without extends objectΒ #48468

Closed

Description

@mjbvz

Bug Report

πŸ”Ž Search Terms

πŸ•— Version & Regression Information

  • Regression in: 4.7.0-dev.20220325
  • Works in 4.7.0-dev.20220323

⏯ Playground Link

Playground currently doesn't show the error:

Playground link

πŸ’» Code

function deepEquals<T>(a: T, b: T): boolean {
    if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
        return false;
    }
    if (Array.isArray(a) || Array.isArray(b)) {
        return false;
    }
    if (Object.keys(a).length !== Object.keys(b).length) { // Error here
        return false;
    }
    return true;
}

πŸ™ Actual behavior

The calls to Object.keys generate errors:

No overload matches this call.
  Overload 1 of 2, '(o: {}): string[]', gave the following error.
    Argument of type 'T' is not assignable to parameter of type '{}'.
  Overload 2 of 2, '(o: object): string[]', gave the following error.
    Argument of type 'T' is not assignable to parameter of type 'object'.

πŸ™‚ Expected behavior

No errors

Activity

  1. mjbvz commented on Mar 29, 2022

    @mjbvz
    Author

    This code previously worked for us in VS Code but is now causing around 11 errors in our codebase. Not sure if simply adding a extends object constraint is the correct fix in all cases

  2. changed the title [-]Types no longer narrowed as expected [/-] [+]Generic type no longer narrowed as expected without `extends object`[/+] on Mar 29, 2022
  3. whzx5byb commented on Mar 29, 2022

    @whzx5byb

    It seems to be caused by #48366, which is tagged as "Breaking Change".

    Now you can't assign a unconstrained type parameter T to T extends {} or T extends object. See also #37388

  4. DanielRosenwasser commented on Mar 29, 2022

    @DanielRosenwasser
    Member

    CC Wesley Wigham (@weswigham)

    Looks like the following will work

    function deepEquals<T extends {} | null | undefined>(a: T, b: T): boolean {
        if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
            return false;
        }
        if (Array.isArray(a) || Array.isArray(b)) {
            return false;
        }
        if (Object.keys(a).length !== Object.keys(b).length) {
            return false;
        }
        return true;
    } 

    and so will

    function deepEquals<T extends object | null | undefined>(a: T, b: T): boolean {
        if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
            return false;
        }
        if (Array.isArray(a) || Array.isArray(b)) {
            return false;
        }
        if (Object.keys(a).length !== Object.keys(b).length) {
            return false;
        }
        return true;
    } 

    but not

    function deepEquals<T extends unknown>(a: T, b: T): boolean {
        if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
            return false;
        }
        if (Array.isArray(a) || Array.isArray(b)) {
            return false;
        }
        if (Object.keys(a).length !== Object.keys(b).length) {
            return false;
        }
        return true;
    } 

    Note that the first two only likely work due to Anders Hejlsberg (@ahejlsberg)'s change in #43183. I don't know if we're willing to do something special for unknown or all non-unions.

  5. mjbvz commented on Mar 29, 2022

    @mjbvz
    Author

    Also seeing this in the @playwright/test package. See this issue for repo steps and details about the error

  6. DanielRosenwasser commented on Apr 5, 2022

    @DanielRosenwasser
    Member

    Playing around with a fix here FWIW

    #48576

  7. arnold-O commented on Feb 9, 2023

    @arnold-O

    The Generic type without constraint is vague, hence with the extends keyword you clearly state what you want that Generic type to be, which is the reason for Typescript.

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

Metadata

Metadata

Labels

Breaking ChangeWould introduce errors in existing codeFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions