Skip to content

fix: add generic info for methods with thisArg of built-in classes - #12784

Merged
Nathan Shively-Sanders (sandersn) merged 4 commits into
microsoft:masterfrom
e-cloud:fix/generic-this-arg
Mar 10, 2017
Merged

fix: add generic info for methods with thisArg of built-in classes#12784
Nathan Shively-Sanders (sandersn) merged 4 commits into
microsoft:masterfrom
e-cloud:fix/generic-this-arg

Conversation

@e-cloud

@e-cloud Scott (e-cloud) commented Dec 9, 2016

Copy link
Copy Markdown
Contributor

Here's a checklist you might find useful.

  • There is an associated issue that is labelled
    'Bug' or 'Accepting PRs' or is in the Community milestone
  • Code is up-to-date with the master branch
  • You've successfully run jake runtests locally
  • You've signed the CLA
  • There are new or updated unit tests validating the change

Fixes #12548

Note: need guide to write tests

@msftclas

Hi Scott (@e-cloud), I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!

In order for us to evaluate and accept your PR, we ask that you sign a contribution license agreement. It's all electronic and will take just minutes. I promise there's no faxing. https://cla.microsoft.com.

TTYL, MSBOT;

@e-cloud

Scott (e-cloud) commented Dec 9, 2016

Copy link
Copy Markdown
Contributor Author

Daniel Rosenwasser (@DanielRosenwasser) Mohamed Hegazy (@mhegazy) can you have a review and give some guide on how to write corresponding tests?

@msftgits

Copy link
Copy Markdown

Hi, I am closing and re-opening this PR to bump the CLA bot. Sorry for the inconvenience!

@msftclas

Hi Scott (@e-cloud), I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by Microsoft and real humans are currently evaluating your PR.

TTYL, MSBOT;

@e-cloud

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) can you take a review?

@sandersn

Copy link
Copy Markdown
Member

The change looks good. To write tests, add a file named something like thisTypeInLib.ts in the directory tests/cases/compiler. In the file, put code from the bug you reported in #12548.

Run gulp runtests-parallel then look in tests/baselines/local to make sure that no .errors.txt file exists and also that the .types file is correct. Then run gulp baseline-accept to put the baselines in the reference directory, ready to commit.

After you get that workflow going, you should add tests for all the functions you improved too.
These instructions are explained better in CONTRIBUTING.md; I'm just summarising from memory.

@e-cloud
Scott (e-cloud) force-pushed the fix/generic-this-arg branch 2 times, most recently from 4339c9a to c42a029 Compare December 17, 2016 11:22
@e-cloud

Copy link
Copy Markdown
Contributor Author

Nathan Shively-Sanders (@sandersn), new test is added. And i find out the new version of tslint breaks down the linting task.

options.every(function (val, index) {
return val === this.options[index];
}, this);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you also add tests for ReadonlyArray and the other U?.*Array types?

}

test(options: string[]) {
options.some(function (val, index) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add tests for find, findIndex, forEach, map?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid that it would be problem. As the default compile target seems to be es5. Then it prompts that find not exist in xxx[]. Should we create a test project for it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can change the target with a line at the beginning: // @target: es6

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your guidance is helpful

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for the other array methods and array types?

@mhegazy

Copy link
Copy Markdown
Contributor

should not this be two overloads:

find(predicate: (this: undefined, value: T, index: number, obj: Array<T>) => boolean): T | undefined;
find<U>(predicate: (this: U, value: T, index: number, obj: Array<T>) => boolean, thisArg : U): T | undefined; 

the spec seems to indicate that, here is what MDN has to say: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

@mhegazy

Copy link
Copy Markdown
Contributor

Also there are other find functions for TypedArrays that should be updated as well.. e.g. Int32Array

@e-cloud

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) what do you mean by:

should not this be two overloads:

find(predicate: (this: undefined, value: T, index: number, obj: Array<T>) => boolean): T | undefined;
find<U>(predicate: (this: U, value: T, index: number, obj: Array<T>) => boolean, thisArg : U): T | >undefined; 

the spec seems to indicate that, here is what MDN has to say: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Can you describe it more clearly?

@mhegazy

Copy link
Copy Markdown
Contributor

From MDN:

If a thisArg parameter is provided to find, it will be used as the this for each invocation of the callback. If it is not provided, then undefined is used.

if this is not defined on a function it is inferred to be any. but the behavior at runtime is that if thisArg is not specified as parameter, the this for the callback is undefined.

@e-cloud

Scott (e-cloud) commented Dec 24, 2016

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) I see what you mean now.

I have some tries and find out some problems.

TL;DR

Use the forEach of Array<T> in the testfile as example.
with the test code:

class A {
    options: string[];

    addOptions(options: string[]) {
        if (!this.options) {
            this.options = [];
        }
        options.forEach(function (item) {
            this.options.push(item);
        }, this);
        return this;
    }

    testUndefined(options: string[]) {
        options.forEach(function (item) {
            this.options.push(item);
        }); // case1
        options.forEach(function (item) {
            this.options.push(item);
        }, undefined); // case2
        options.forEach(function (item) {
            this.options.push(item);
        }, null); // case3
    }
}

in lib/es5.d.ts

  1. version 1
forEach(callbackfn: (this: undefined, value: T, index: number, array: T[]) => void): void;
forEach<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => void, thisArg: Z): void;

erros from thisTypeInNativeThisAssignableMethods.errors.txt:

        testUndefined(options: string[]) {
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'undefined'.
            }); // case1
            options.forEach(function (item) {
                this.options.push(item);
            }, undefined); // case2
            options.forEach(function (item) {
                this.options.push(item);
            }, null); // case3
        }

only case1 will be recognized as error, others are legal rather than error.

  1. version 2
    forEach(callbackfn: (this: undefined, value: T, index: number, array: T[]) => void): void;
    forEach(callbackfn: (this: undefined, value: T, index: number, array: T[]) => void, thisArg: undefined): void;
    forEach<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => void, thisArg: Z): void;

erros from thisTypeInNativeThisAssignableMethods.errors.txt:

        testUndefined(options: string[]) {
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'undefined'.
            }); // case1
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'undefined'.
            }, undefined); // case2
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'undefined'.
            }, null); // case3
        }

now all are recognized as undefined, even case3's this should be null.

  1. version 3
    forEach(callbackfn: (this: undefined, value: T, index: number, array: T[]) => void): void;
    forEach(callbackfn: (this: null, value: T, index: number, array: T[]) => void, thisArg: null): void;
    forEach(callbackfn: (this: undefined, value: T, index: number, array: T[]) => void, thisArg: undefined): void;
    forEach<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => void, thisArg: Z): void;

erros from thisTypeInNativeThisAssignableMethods.errors.txt:

        testUndefined(options: string[]) {
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'undefined'.
            }); // case1
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'null'.
            }, undefined); // case2
            options.forEach(function (item) {
                this.options.push(item);
                     ~~~~~~~
!!! error TS2339: Property 'options' does not exist on type 'null'.
            }, null); // case3
        }

It seems the nulled-this interface takes precedence. And if the undefined version is placed before null version, the errors are same as version 2


In short

undefined and null is recognized as same. And when undefined is assigned as generic type, it's inferred as any.

Personally, I'm ok with version 2 for temporary solution. Mohamed Hegazy (@mhegazy) do you agree to make three interfaces for all involved methods?

@e-cloud

Copy link
Copy Markdown
Contributor Author

Another thing worths mentioning is that the from static method of all array constructor. They should support iterable parameter and the generic info of Array like is not proper. I just find it accidentally just because from's mapfn also supports thisArg.

@e-cloud

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) Nathan Shively-Sanders (@sandersn) I've rewrite the PR to accomplish the solution commented above.

Note: I use several regexps to globally update the interfaces. Manually updating per interface would be nightmare.

@sandersn

Copy link
Copy Markdown
Member

Note that option 1 works correctly with // strictNullChecks: true. However, option 2 is OK with me since it provides better errors with strict null checks off. Mohamed Hegazy (@mhegazy), are you OK with this solution as well?

@e-cloud

Copy link
Copy Markdown
Contributor Author

Nathan Shively-Sanders (@sandersn) It seems to fail many tests. Should I update the other baseline files? It would be a lot of files.

@sandersn

Copy link
Copy Markdown
Member

Yes, the baseline changes are expected: when you add overloads, it changes the symbols and types to reflect those additional overloads, even if the types resulting from the call doesn't change.

@e-cloud

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) Nathan Shively-Sanders (@sandersn) hey, guys. All checks have passed now. How about have a review again?

@e-cloud

Copy link
Copy Markdown
Contributor Author

Mohamed Hegazy (@mhegazy) request for review

@alitaheri

Copy link
Copy Markdown

You have a yarn.lock file checked in. Should it be there?

@e-cloud

Scott (e-cloud) commented Jan 24, 2017

Copy link
Copy Markdown
Contributor Author

Yes, accident. 😭

when enabling `noImplicitThis`, if assing this argument for
methods like `array.forEach` will cause compilation error.
This commit fixes it.

fix microsoft#12548
@e-cloud

Copy link
Copy Markdown
Contributor Author

Nathan Shively-Sanders (@sandersn) ,I've rebased on master just now. Can you guys give some review and feedback? Coz it's been a while since starting the PR.

The tests failed because of the latest problematic @types/node module. The tests passed locally when i switch @types/node to v6.x.

@mhegazy

Copy link
Copy Markdown
Contributor

I have a fix in master now for the build break. can you give it another try.

@e-cloud

Copy link
Copy Markdown
Contributor Author

tests pass now.

@sandersn
Nathan Shively-Sanders (sandersn) merged commit bdb6a8a into microsoft:master Mar 10, 2017
@sandersn

Copy link
Copy Markdown
Member

Thanks Scott (@e-cloud)!

@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants