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

Type this in more constructor functions #39447

Merged
merged 6 commits into from
Jul 8, 2020
Merged

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Jul 6, 2020

Previously, this: this in constructor functions only when there was an explicit @constructor tag on the function. Now, this: this for any function that's known to be a constructor function.

This improves completions inside constructor functions; also note that previously the compiler did type this: this inside methods of constructor functions, so this fix makes us more consistent. This is reflected in the large number of baselines that improve.

The fix is a simple switch to isJSConstructor, which is the standard way to detect constructor functions. I'm not sure why the original PR didn't use this method.

I remember discussing this limitation in the original bug, #25979, and I guess I decided that it made sense. But I was heavily primed by the bug's framing of the problem in terms of noImplicitThis, which should require an explicit @constructor tag.

  • With better typing comes better detection of @readonly assignment; I had to fix the readonly detection code to use isJSConstructor as well.
  • Also, previously, both @class and @this in a jsdoc would cause the @this annotation to be ignored. This became a worse problem with this PR, because this is correctly typed even without the annotation. This PR now makes sure that @this is checked first and used if present.

Fixes #18171

Previously,  `this: this` in constructor functions only when there was
an explicit `@constructor` tag on the function. Now, `this: this` for
any function that's known to be a constructor function.

This improves completions inside constructor functions; also note that
previously the compiler *did* type `this: this` inside methods of constructor
functions, so this fix makes us more consistent. This is reflected in
the large number of baselines that improve.

The fix is a simple switch to `isJSConstructor`, which is the standard
way to detect constructor functions. I'm not sure why the original PR
didn't use this method.

I remember discussing this limitation in the original bug, #25979, and
I guess I decided that it made sense. But I was heavily primed by the bug's
framing of the problem in terms of `noImplicitThis`, which *should*
require an explicit `@constructor` tag.

With better typing comes better detection of `@readonly` assignment; I
had to fix the readonly detection code to use `isJSConstructor` as well.
getJSDocClassTag(container)) {
const classType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType!;
return getFlowTypeOfReference(node, classType);
else if (isJSConstructor(container)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the real change of the PR; the rest just dedupes getFlowTypeOfReference calls to the bottom of the function.

@sandersn
Copy link
Member Author

sandersn commented Jul 6, 2020

woops, missed some fourslash tests since those don't get baseline updated.

The new rules mean that it never applies. It's possible that it should
apply to functions like

```js
function f() {
  this.init()
}
```

In which `init` is never defined, but I think this program is incomplete
enough that not offering the fix is fine.
@sandersn
Copy link
Member Author

sandersn commented Jul 6, 2020

The fourslash tests point out a codefix that doesn't apply anymore; with this change this is usually typed in constructor functions, so explicit marking with @class is less often needed.

I'm not sure if this contradicts the original spirit of #25979, in which we decided that noImplicitThis: true implies that the codebase wants to make constructor functions less convenient by forcing annotation, but it's way more convenient, and it's useful in the usual case where noImplicitThis: false in JS.

It's possible to get back the original noImplicitThis: true error+codefix by issuing a noImplicitThis-only error while still returning the correct type, but I don't really think it's worth the complexity in the code.

(Edit: Or the effort of writing a new error message.)

@sandersn
Copy link
Member Author

sandersn commented Jul 6, 2020

Now master is broken with some lint. I'll merge it in again once it's fixed.

@sandersn
Copy link
Member Author

sandersn commented Jul 6, 2020

@rachelgshaffer you might be interested in this as well, especially now that it changes a codefix.

@sandersn
Copy link
Member Author

sandersn commented Jul 6, 2020

In fact, it looks like I imagined requesting reviewers at all, because the reviewer list is broken. @andrewbranch @elibarzilay can you take a look?

Copy link
Member

@andrewbranch andrewbranch left a comment

Choose a reason for hiding this comment

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

👍👍 The old behavior has confused me in the past. That said, are you concerned about this being a breaking change, since the result is a lot of any changing to not any?

@sandersn
Copy link
Member Author

sandersn commented Jul 7, 2020

Since it only affects JS files, for non-checkJS compiles, nobody will notice anything. For checkJS compiles, which are a lot rarer, I think the improvement is worth it.

Also, the this type is used everywhere except constructor function bodies, so it's not like we're exposing a new code path to the world.
Edit: (That's what makes the old way so confusing, too.)

However, it's a good idea to check this on the user tests...

@typescript-bot user test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jul 7, 2020

Heya @sandersn, I've started to run the parallelized community code test suite on this PR at 5b083a0. You can monitor the build here.

@andrewbranch
Copy link
Member

Also, the this type is used everywhere except constructor function bodies, so it's not like we're exposing a new code path to the world.

Ah, I didn’t realize that. That makes it seem like a smaller change then.

@typescript-bot
Copy link
Collaborator

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

@sandersn
Copy link
Member Author

sandersn commented Jul 7, 2020

Briefly: yes, it is pretty breaky. I'm going to see what the breaks look like before merging.

@sandersn
Copy link
Member Author

sandersn commented Jul 7, 2020

acorn: acorn's distributed code uses a weird pattern that aliases the prototype like so

let pp$1 = Parser.prototype
pp$1.method = function() { ... }

Then any method with a this-property assignment, which is most of them, treats the method as a fresh constructor function instead.

It's fine that we don't understand that pattern, but this problem will happen anywhere that we misunderstand a method as a constructor function.

bluebird: manual calling of a function with this causes the same break:

function succeed() {
    return finallyHandler.call(this, this.promise._target()._settledValue());
}
function finallyHandler(reasonOrValue) {
    var promise = this.promise;
    var handler = this.handler;
    ....

chrome-devtools-frontend: BUG: @this is now ignored.

aliases of this don't register as this-property assignments, and this now causes lots of error if you have at least one non-aliased this-property assignment:

function Display(place, doc, input) {
  var d = this;
  this.input = input; // ok
  d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); // error!
  ...

puppeteer: side note: puppeteer's user test is broken because of a master → main switch.

@sandersn
Copy link
Member Author

sandersn commented Jul 7, 2020

The @this bug is actually an existing conflict that you can expose today with

/**
 * @class
 * @this {{ e: 1, m: 1 }}
 */
function C() {
  this.e = this.m + 1
}

I think it's pretty easy to fix, though.

Previously, both `@class` and `@this` in a jsdoc would cause the `@this`
annotation to be ignored. This became a worse problem with this PR,
because `this` is correctly typed even without the annotation.

This commit makes sure that `@this` is checked first and used if
present.
@sandersn
Copy link
Member Author

sandersn commented Jul 8, 2020

@typescript-bot user test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jul 8, 2020

Heya @sandersn, I've started to run the parallelized community code test suite on this PR at aa5af56. You can monitor the build here.

@sandersn
Copy link
Member Author

sandersn commented Jul 8, 2020

The @this fix improves devtools-frontend's errors, but adds some more errors where the code extends built-ins with an explicit @this comment that we previously ignored. I'm going to look at the rest of the new errors now.

follow-redirects: the inheritance pattern Base.call(this) as the first line of a constructor function gives an error now.
graceful-fs: same
minimatch: similar to acorn, except

Minimatch.prototype.make = make
function make() { ... }

npmlog: just a few more errors in instance methods that we mistakenly think are constructor functions. We don't bind instance methods, but these functions now have errors as well.

uglify-js: when your OO metaprogramming has functions named defun, we have no responsibility to understand what you're doing at all.

@sandersn
Copy link
Member Author

sandersn commented Jul 8, 2020

So, to summarise: There are lots of new errors, mostly in patterns we already didn't understand, or attempt to understand. Before, this: any, but now this: this.

I think this is fine because the typical case of checkJs: false will just get improved completions, and people with checkJs: true are likely to use newer constructs like classes, based on survey data from 2018 at least.

@sandersn sandersn merged commit 53a756e into master Jul 8, 2020
@sandersn sandersn deleted the better-js-constructor-checks branch July 8, 2020 15:44
cangSDARM added a commit to cangSDARM/TypeScript that referenced this pull request Jul 9, 2020
* upstream/master: (75 commits)
  Insert auto-imports in sorted order (microsoft#39394)
  LEGO: check in for master to temporary branch.
  Better checking of @param/@Property tags (microsoft#39487)
  fix(25155): add space before optional parameters/properties (microsoft#38798)
  Add regression test for microsoft#38834 (microsoft#39479)
  Fixes searches for symbols exported using export * as (microsoft#39507)
  fix(39421): omit prefix text for rest binding element (microsoft#39433)
  fix(39440): show QF for abstract classes with methods which include 'this' parameter (microsoft#39465)
  Remove unnecessary assert (microsoft#39483)
  LEGO: check in for master to temporary branch.
  Update user baselines (microsoft#39220)
  Type `this` in more constructor functions (microsoft#39447)
  LEGO: check in for master to temporary branch.
  LEGO: check in for master to temporary branch.
  Properly handle rest parameters in function declarations with @type annotations (microsoft#39473)
  Ensure type/namespaceish statics are included in the list of namespace merge members (microsoft#38920)
  Fix getTypeAtLocation for dotted implements clauses (microsoft#39363)
  Add workflow_dispatch to our nightly publish script. (microsoft#39485)
  Fix crash in decorator metadata calculation when serializing template literal type nodes (microsoft#39481)
  Fix test semantic merge conflict between microsoft#39348 and microsoft#39130 (microsoft#39478)
  ...

# Conflicts:
#	src/compiler/scanner.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

In JS, constructor functions without property assignments aren't recognised as classes
3 participants