Definitelytyped: _.chain from `@types/lodash` is broken on Typescript 2.6

Created on 2 Nov 2017  Â·  20Comments  Â·  Source: DefinitelyTyped/DefinitelyTyped

_.chain from @types/lodash is broken on Typescript 2.6

I have little knowledge of lodash internals, but the project that I'm working on uses it a lot. Using the new @ts-ignore silences the error, but I wanted to register it here.

Reproducing:

install typescript ~2.6.0
tsc -init
npm i lodash @types/lodash --save
index.ts:

import * as _ from 'lodash'

var a = _.chain([1,2,3]).map((x) => { return x + 1 })

console.log(a.value())

run tsc

Result:

index.ts(3,9): error TS2684: The 'this' context of type 'LoDashExplicitWrapper<number[]>' is not assignable to method's 'this' of type 'LoDashExplicitWrapper<ArrayLike<number> | Dictionary<number> | NumericDictionary<number> | null |...'.
  Types of property 'push' are incompatible.
    Type '<T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | null | undefined>, ...items: T[]) => _.LoDashExp...' is not assignable to type '<T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | null | undefined>, ...items: T[]) => _.LoDashExp...'. Two different types with this name exist, but they are unrelated.
      Type 'LoDashExplicitWrapper<number[]>' is not assignable to type 'LoDashExplicitWrapper<ArrayLike<number> | Dictionary<number> | NumericDictionary<number> | null |...'.
index.ts(3,31): error TS7006: Parameter 'x' implicitly has an 'any' type.

Dependencies:

    "@types/lodash": "^4.14.80",
    "lodash": "^4.17.4"

@aj-r, @thorn0, @andy-ms any ideas?

Most helpful comment

Ok, I got this code to compile with strictFunctionTypes enabled by removing the offending toArray definition:

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const wrapper2: _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined> = wrapper1;

However, the following code does NOT compile:

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const result1 = wrapper1.max(); // Error: Type _.LoDashImplicitWrapper<number[]> is not assignable to type _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined>

Which seems strange, since we just proved in the first example that the types ARE assignable. But don't worry, it gets stranger: If I insert the wrapper2 line from the first example BEFORE the result1 line in the second example, the code compiles!

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const wrapper2: _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined> = wrapper1;
const result1 = wrapper1.max(); // This is magically okay now

Note that I don't actually use wrapper2 anywhere, but it somehow affects whether line 3 compiles or not. It seems like typescript caches whether a type is assignable to another, and line 2 puts this cache in a good state, allowing line 3 to compile.

Typescript seems to determine assignability differently for basic assignments (e.g. wrapper2 = wrapper1 ) vs. this parameters (e.g. wrapper1.max()). @andy-ms could this be a bug in the typescript compiler?

For reference, here is the definition for max:

max<T>(this: LoDashImplicitWrapper<List<T> | null | undefined>): T | undefined;

All 20 comments

Weird. LoDashExplicitWrapper<number[]> should be assignable to LoDashExplicitWrapper<ArrayLike<number> | ...>, since number[] is assignable to ArrayLike<number>. Did something change with assignability in TypeScript 2.6?

Note that this is a strictFunctionTypes error, but that option is not yet enabled in the lodash package here.
I'm wondering why the methods in interface LoDashExplicitWrapper<TValue> have a this parameter at all? Why don't they use TValue anywhere?

Good catch @andy-ms.
strictFunctionTypes is enabled by default with strict option in Typescript 2.6.
I'll disable it for now in my project... much better than @ts-ignore

@andy-ms Some of the methods in LoDashExplicitWrapper<TValue> do use TValue (value(), debounce(), wrap(), and more). However, most functions use a this parameter to enforce stricter types on what kind of chain you can use.

In the case of map, we use this: LoDashExplicitWrapper<ArrayLike<T> | Dictionary<T> | null | undefined> because we want to allow things like:

_.chain([1, 2, 3]).map();

but not:

_.chain(5).map();

But it's perfectly acceptable to use _.chain(5) with other functions, e.g.

_.chain(5).constant();

The this parameter also helps us determine other constraints properly. In the case of map, it lets us constrain the parameter types of iteratee, which we could not do with just TValue.

However I'm confused about this strictFunctionTypes error:

Type '<T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | null | undefined>, ...items: T[]) => _.LoDashExp...' is not assignable to type '<T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | null | undefined>, ...items: T[]) => _.LoDashExp...'. Two different types with this name exist, but they are unrelated.

How are they 2 different types with the same name? It seems to me that they are not only related, but they are exactly the same type. LoDashExplicitWrapper only defines one push function, so I don't see how there could be 2 different types with the same name.

The last error also confuses me (Type 'LoDashExplicitWrapper<number[]>' is not assignable to type 'LoDashExplicitWrapper<ArrayLike<number> | Dictionary<number> | NumericDictionary<number> | null |...'.) but I assume this is due to the same "two different types with the same name" issue.

That's due to Microsoft/TypeScript#19373

The technique/hack with overloads having different types for this is described here: https://github.com/Microsoft/TypeScript/issues/1290 Apparently, strictFunctionTypes breaks it.

It looks like the real problem here is that LoDashExplicitWrapper<number[]> really is not assignable to type LoDashExplicitWrapper<ArrayLike<number> | Dictionary<number> | NumericDictionary<number> | null | undefined> if strictFunctionTypes is enabled.

I tested by writing this line of code:

const test: _.LoDashImplicitWrapper<ArrayLike<number>> = _([1,2,3]);

Typescript fails with this error, which gives a bit more detail than our original error:

src\test.ts (8,7): Type 'LoDashImplicitWrapper<number[]>' is not assignable to type 'LoDashImplicitWrapper<ArrayLike<number>>'.
  Types of property 'chain' are incompatible.
    Type '() => LoDashExplicitWrapper<number[]>' is not assignable to type '() => LoDashExplicitWrapper<ArrayLike<number>>'.
      Type 'LoDashExplicitWrapper<number[]>' is not assignable to type 'LoDashExplicitWrapper<ArrayLike<number>>'.
        Types of property 'debounce' are incompatible.
          Type '(wait?: number | undefined, options?: DebounceSettings | undefined) => LoDashExplicitWrapper<numb...' is not assignable to type '(wait?: number | undefined, options?: DebounceSettings | undefined) => LoDashExplicitWrapper<Arra...'.
            Type 'LoDashExplicitWrapper<number[] & Cancelable>' is not assignable to type 'LoDashExplicitWrapper<ArrayLike<number> & Cancelable>'.
              Types of property 'memoize' are incompatible.
                Type '(resolver?: ((...args: any[]) => any) | undefined) => LoDashExplicitWrapper<number[] & Cancelable...' is not assignable to type '(resolver?: ((...args: any[]) => any) | undefined) => LoDashExplicitWrapper<ArrayLike<number> & C...'.
                  Type 'LoDashExplicitWrapper<number[] & Cancelable & MemoizedFunction>' is not assignable to type 'LoDashExplicitWrapper<ArrayLike<number> & Cancelable & MemoizedFunction>'.
                    Types of property 'toArray' are incompatible.
                      Type '{ <T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | _.Dictionary<T> | _.NumericDictionary<T> | nul...' is not assignable to type '{ <T>(this: _.LoDashExplicitWrapper<ArrayLike<T> | _.Dictionary<T> | _.NumericDictionary<T> | nul...'. Two different types with this name exist, but they are unrelated.
                        Type 'LoDashExplicitWrapper<(number | (() => string) | (() => void) | MapCache | ((...items: number[]) ...' is not assignable to type 'LoDashExplicitWrapper<(number | (() => void) | MapCache)[]>'.
                          Types of property 'thru' are incompatible.
                            Type '<TResult>(interceptor: (value: (number | (() => string) | (() => void) | MapCache | ((...items: n...' is not assignable to type '<TResult>(interceptor: (value: (number | (() => void) | MapCache)[]) => TResult) => LoDashExplici...'.
                              Types of parameters 'interceptor' and 'interceptor' are incompatible.
                                Types of parameters 'value' and 'value' are incompatible.
                                  Type '(number | (() => string) | (() => void) | MapCache | ((...items: number[]) => number) | (() => nu...' is not assignable to type '(number | (() => void) | MapCache)[]'. (2322)

This led me to what I think is the culprit:

interface LoDashExplicitWrapper<TValue> {
        toArray(): LoDashExplicitWrapper<Array<TValue[keyof TValue]>>;
}

I think the TValue[keyof TValue] part is throwing typescript off. I'll try to make a PR that fixes this.

Ok, I got this code to compile with strictFunctionTypes enabled by removing the offending toArray definition:

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const wrapper2: _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined> = wrapper1;

However, the following code does NOT compile:

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const result1 = wrapper1.max(); // Error: Type _.LoDashImplicitWrapper<number[]> is not assignable to type _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined>

Which seems strange, since we just proved in the first example that the types ARE assignable. But don't worry, it gets stranger: If I insert the wrapper2 line from the first example BEFORE the result1 line in the second example, the code compiles!

const wrapper1: _.LoDashImplicitWrapper<number[]> = _([1, 2, 3]);
const wrapper2: _.LoDashImplicitWrapper<ArrayLike<number> | null | undefined> = wrapper1;
const result1 = wrapper1.max(); // This is magically okay now

Note that I don't actually use wrapper2 anywhere, but it somehow affects whether line 3 compiles or not. It seems like typescript caches whether a type is assignable to another, and line 2 puts this cache in a good state, allowing line 3 to compile.

Typescript seems to determine assignability differently for basic assignments (e.g. wrapper2 = wrapper1 ) vs. this parameters (e.g. wrapper1.max()). @andy-ms could this be a bug in the typescript compiler?

For reference, here is the definition for max:

max<T>(this: LoDashImplicitWrapper<List<T> | null | undefined>): T | undefined;

I have this issue on my production project and I'd rather not have to turn off strictFunctionTypes. Was there any kind of workaround found besides turning off that option? If i'm reading correctly I think the answer is no but I wanted to make sure.

Unfortunately turning off strictFunctionTypes is the only option right now.
I tried making a fix, but there seems to be a bug in Typescript.

On Nov 17, 2017 1:03 PM, "Lily Carpenter" notifications@github.com wrote:

I have this issue on my production project and I'd rather not have to turn
off strictFunctionTypes. Was there any kind of workaround found besides
turning off that option? If i'm reading correctly I think the answer is no
but I wanted to make sure.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/21206#issuecomment-345317953,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGpesyBND2jTPD8UIDELdLCnUs-nJwU_ks5s3cpWgaJpZM4QQGTb
.

@aj-r To confirm, _.chain is unusable with TypeScript >= 2.6.0 and strictFunctionTypes=true until https://github.com/Microsoft/TypeScript/issues/19373 is resolved?

@bcherny @azrazalea I think I've found a workaround to make the lodash types work for now. I'll try to make a PR with this workaround soon.

I don't think Microsoft/TypeScript#19373 will fix this by itself - I think that will only help point us at the real problem. As it is, I'm diagnosing the problem by deleting/adding a few functions at a time until I find the source, which is a little slow.

The typescript bug I described in my previous comment seems to be fixed in 2.7. I was using 2.6 when I wrote that comment.

I'm running into this problem in Typescript 2.5.2 as well

I'm having the same issue with Typescript 2.4.2, this is not limited to 2.6+

Can you elaborate? This issue was caused by strictFunctionTypes which is a
2.6 feature. If you're still having issues in 2.4, you should probably
create a separate bug. Also make sure you're using the latest lodash
typings.

On Jan 26, 2018 11:34 AM, "David Barreto" notifications@github.com wrote:

I'm having the same issue with Typescript 2.4.2, this is not limited to
2.6+

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/21206#issuecomment-360834828,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGpes6l9teLnjJVtatG8D8j1UsuodHDXks5tOf6hgaJpZM4QQGTb
.

Any updates on this issue?

Still seeing the problem in TS 2.8.3

@swayam18 What exactly doesn't work as expected? Please be specific.

@thorn0 Apologies, did not realize that my settings were incorrect. Please disregard my comment. Thanks!

Was this page helpful?
0 / 5 - 0 ratings