Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Mar 10, 2017

Conversation

e-cloud
Copy link
Contributor

@e-cloud e-cloud commented Dec 9, 2016

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
Copy link

msftclas commented Dec 9, 2016

Hi @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
Copy link
Contributor Author

e-cloud commented Dec 9, 2016

@DanielRosenwasser @mhegazy can you have a review and give some guide on how to write corresponding tests?

@msftgits
Copy link

msftgits commented Dec 9, 2016

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

@msftgits msftgits closed this Dec 9, 2016
@msftgits msftgits reopened this Dec 9, 2016
@msftclas
Copy link

msftclas commented Dec 9, 2016

Hi @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
Contributor Author

e-cloud commented Dec 15, 2016

@mhegazy can you take a review?

@sandersn
Copy link
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 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
Contributor Author

e-cloud commented Dec 17, 2016

@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
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
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
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
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
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
Member

@sandersn sandersn left a comment

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
Contributor

mhegazy commented Dec 22, 2016

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
Contributor

mhegazy commented Dec 22, 2016

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

@e-cloud
Copy link
Contributor Author

e-cloud commented Dec 22, 2016

@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
Contributor

mhegazy commented Dec 22, 2016

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
Copy link
Contributor Author

e-cloud commented Dec 24, 2016

@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. @mhegazy do you agree to make three interfaces for all involved methods?

@e-cloud
Copy link
Contributor Author

e-cloud commented Dec 24, 2016

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
Contributor Author

e-cloud commented Jan 5, 2017

@mhegazy @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
Member

sandersn commented Jan 5, 2017

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. @mhegazy, are you OK with this solution as well?

@e-cloud
Copy link
Contributor Author

e-cloud commented Jan 6, 2017

@sandersn It seems to fail many tests. Should I update the other baseline files? It would be a lot of files.

@sandersn
Copy link
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
Contributor Author

e-cloud commented Jan 11, 2017

@mhegazy @sandersn hey, guys. All checks have passed now. How about have a review again?

@e-cloud
Copy link
Contributor Author

e-cloud commented Jan 24, 2017

@mhegazy request for review

@alitaheri
Copy link

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

@e-cloud
Copy link
Contributor Author

e-cloud commented Jan 24, 2017

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
Contributor Author

e-cloud commented Mar 9, 2017

@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
Contributor

mhegazy commented Mar 9, 2017

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

@e-cloud
Copy link
Contributor Author

e-cloud commented Mar 10, 2017

tests pass now.

@sandersn sandersn merged commit bdb6a8a into microsoft:master Mar 10, 2017
@sandersn
Copy link
Member

Thanks @e-cloud!

@mhegazy mhegazy mentioned this pull request Jun 4, 2017
@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.

None yet

6 participants