Skip to content

Avoid circular reference in this-property assignments #37827

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

Merged

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Apr 7, 2020

To do this, don't check this-property assigments that have the
this-property of the lhs appearing somewhere on the rhs:

class C {
  m() {
    this.x = 12
    this.x = this.x + this.y
  }
}

I tried suppressing the circularity error, but because we cache the first type discovered for a property, this still results in an implicit any for x in the previous example. It just doesn't have an error.

I'm not sure how expensive the tree walk is, or how much javascript projects will benefit from this change.

Unfortunately, the perf tests won't help with the former, but the user tests should help with the latter.

Fixes #35099

To do this, don't check this-property assigments that have the
this-property of the lhs appearing somewhere on the rhs:

```js
class C {
  m() {
    this.x = 12
    this.x = this.x + this.y
  }
}
```

I tried suppressing the circularity error, but because we cache the
first type discovered for a property, this still results in an implicit
any for `x` in the previous example. It just doesn't have an error.

Fixes #35099
@sandersn
Copy link
Member Author

sandersn commented Apr 7, 2020

@typescript-bot perf test this
@typescript-bot user test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 7, 2020

Heya @sandersn, I've started to run the perf test suite on this PR at b518ea0. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 7, 2020

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

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

@typescript-bot
Copy link
Collaborator

@sandersn
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..37827

Metric master 37827 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 327,631k (± 0.02%) 327,089k (± 0.04%) -542k (- 0.17%) 326,652k 327,384k
Parse Time 1.64s (± 0.54%) 1.63s (± 0.36%) -0.01s (- 0.55%) 1.62s 1.64s
Bind Time 0.89s (± 0.56%) 0.89s (± 1.17%) +0.00s (+ 0.34%) 0.86s 0.91s
Check Time 4.77s (± 0.66%) 4.76s (± 0.60%) -0.01s (- 0.25%) 4.69s 4.80s
Emit Time 5.33s (± 0.55%) 5.30s (± 0.56%) -0.03s (- 0.49%) 5.22s 5.37s
Total Time 12.63s (± 0.40%) 12.58s (± 0.45%) -0.04s (- 0.33%) 12.45s 12.68s
Monaco - node (v10.16.3, x64)
Memory used 327,093k (± 0.02%) 327,085k (± 0.02%) -8k (- 0.00%) 326,931k 327,202k
Parse Time 1.26s (± 0.69%) 1.26s (± 0.27%) +0.00s (+ 0.08%) 1.26s 1.27s
Bind Time 0.77s (± 0.62%) 0.77s (± 0.44%) +0.00s (+ 0.00%) 0.77s 0.78s
Check Time 4.76s (± 0.44%) 4.77s (± 0.46%) +0.01s (+ 0.15%) 4.72s 4.83s
Emit Time 2.91s (± 0.53%) 2.93s (± 0.88%) +0.02s (+ 0.55%) 2.88s 3.00s
Total Time 9.71s (± 0.23%) 9.73s (± 0.32%) +0.02s (+ 0.24%) 9.68s 9.83s
TFS - node (v10.16.3, x64)
Memory used 292,022k (± 0.01%) 292,075k (± 0.03%) +53k (+ 0.02%) 291,914k 292,321k
Parse Time 0.96s (± 0.71%) 0.96s (± 0.54%) +0.00s (+ 0.31%) 0.95s 0.97s
Bind Time 0.74s (± 0.66%) 0.74s (± 0.60%) -0.01s (- 0.81%) 0.73s 0.75s
Check Time 4.30s (± 0.48%) 4.29s (± 0.42%) -0.01s (- 0.16%) 4.25s 4.33s
Emit Time 3.07s (± 0.80%) 3.07s (± 0.63%) -0.00s (- 0.13%) 3.03s 3.12s
Total Time 9.07s (± 0.36%) 9.06s (± 0.41%) -0.01s (- 0.13%) 9.00s 9.18s
material-ui - node (v10.16.3, x64)
Memory used 450,581k (± 0.01%) 450,321k (± 0.01%) -261k (- 0.06%) 450,180k 450,476k
Parse Time 1.78s (± 0.20%) 1.78s (± 0.49%) +0.00s (+ 0.23%) 1.77s 1.80s
Bind Time 0.68s (± 0.50%) 0.68s (± 0.87%) +0.00s (+ 0.15%) 0.67s 0.69s
Check Time 12.55s (± 0.25%) 12.58s (± 0.69%) +0.03s (+ 0.23%) 12.43s 12.82s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.01s (± 0.24%) 15.04s (± 0.63%) +0.03s (+ 0.20%) 14.88s 15.31s
Angular - node (v12.1.0, x64)
Memory used 303,216k (± 0.01%) 302,688k (± 0.02%) -528k (- 0.17%) 302,608k 302,901k
Parse Time 1.58s (± 0.37%) 1.58s (± 0.41%) +0.00s (+ 0.32%) 1.57s 1.60s
Bind Time 0.87s (± 0.64%) 0.87s (± 0.66%) +0.00s (+ 0.23%) 0.86s 0.88s
Check Time 4.65s (± 0.52%) 4.63s (± 0.36%) -0.02s (- 0.39%) 4.60s 4.68s
Emit Time 5.49s (± 0.64%) 5.47s (± 0.45%) -0.02s (- 0.29%) 5.44s 5.56s
Total Time 12.59s (± 0.34%) 12.56s (± 0.28%) -0.03s (- 0.22%) 12.49s 12.65s
Monaco - node (v12.1.0, x64)
Memory used 307,049k (± 0.02%) 307,034k (± 0.02%) -15k (- 0.00%) 306,922k 307,175k
Parse Time 1.22s (± 0.47%) 1.21s (± 0.55%) -0.01s (- 0.82%) 1.20s 1.23s
Bind Time 0.74s (± 0.66%) 0.74s (± 0.45%) -0.00s (- 0.54%) 0.73s 0.75s
Check Time 4.56s (± 0.41%) 4.57s (± 0.22%) +0.01s (+ 0.13%) 4.55s 4.59s
Emit Time 2.96s (± 0.79%) 2.95s (± 0.51%) -0.02s (- 0.57%) 2.92s 2.98s
Total Time 9.49s (± 0.43%) 9.47s (± 0.18%) -0.02s (- 0.21%) 9.43s 9.50s
TFS - node (v12.1.0, x64)
Memory used 274,297k (± 0.02%) 274,297k (± 0.02%) -0k (- 0.00%) 274,161k 274,412k
Parse Time 0.94s (± 0.81%) 0.93s (± 0.73%) -0.00s (- 0.11%) 0.92s 0.95s
Bind Time 0.71s (± 1.38%) 0.71s (± 1.65%) -0.00s (- 0.14%) 0.69s 0.75s
Check Time 4.21s (± 0.42%) 4.21s (± 0.47%) +0.00s (+ 0.12%) 4.17s 4.25s
Emit Time 3.11s (± 0.82%) 3.09s (± 0.77%) -0.02s (- 0.55%) 3.03s 3.15s
Total Time 8.97s (± 0.36%) 8.95s (± 0.33%) -0.02s (- 0.17%) 8.90s 9.03s
material-ui - node (v12.1.0, x64)
Memory used 428,118k (± 0.02%) 427,831k (± 0.01%) -287k (- 0.07%) 427,737k 427,957k
Parse Time 1.76s (± 0.54%) 1.76s (± 0.48%) +0.00s (+ 0.23%) 1.75s 1.79s
Bind Time 0.63s (± 1.11%) 0.63s (± 0.75%) -0.00s (- 0.16%) 0.62s 0.64s
Check Time 11.31s (± 0.76%) 11.24s (± 0.80%) -0.07s (- 0.62%) 11.11s 11.53s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 13.70s (± 0.65%) 13.63s (± 0.64%) -0.07s (- 0.48%) 13.49s 13.91s
Angular - node (v8.9.0, x64)
Memory used 322,506k (± 0.02%) 322,029k (± 0.02%) -477k (- 0.15%) 321,905k 322,142k
Parse Time 2.14s (± 2.54%) 2.11s (± 0.58%) -0.03s (- 1.54%) 2.10s 2.15s
Bind Time 0.92s (± 0.70%) 0.92s (± 0.81%) -0.01s (- 0.54%) 0.90s 0.94s
Check Time 5.46s (± 0.41%) 5.42s (± 0.69%) -0.04s (- 0.79%) 5.28s 5.47s
Emit Time 6.21s (± 0.66%) 6.20s (± 0.97%) -0.01s (- 0.16%) 6.06s 6.38s
Total Time 14.74s (± 0.47%) 14.65s (± 0.39%) -0.09s (- 0.63%) 14.51s 14.76s
Monaco - node (v8.9.0, x64)
Memory used 325,546k (± 0.01%) 325,533k (± 0.02%) -13k (- 0.00%) 325,444k 325,651k
Parse Time 1.55s (± 0.59%) 1.55s (± 0.45%) 0.00s ( 0.00%) 1.53s 1.56s
Bind Time 0.89s (± 0.53%) 0.89s (± 0.90%) -0.00s (- 0.11%) 0.87s 0.91s
Check Time 5.39s (± 0.65%) 5.38s (± 0.41%) -0.01s (- 0.11%) 5.35s 5.43s
Emit Time 3.52s (± 0.46%) 3.52s (± 0.49%) +0.00s (+ 0.09%) 3.48s 3.56s
Total Time 11.35s (± 0.41%) 11.35s (± 0.25%) -0.00s (- 0.04%) 11.28s 11.40s
TFS - node (v8.9.0, x64)
Memory used 291,519k (± 0.01%) 291,507k (± 0.01%) -12k (- 0.00%) 291,458k 291,562k
Parse Time 1.26s (± 0.81%) 1.25s (± 0.27%) -0.01s (- 0.63%) 1.25s 1.26s
Bind Time 0.75s (± 0.78%) 0.74s (± 0.54%) -0.01s (- 0.80%) 0.73s 0.75s
Check Time 4.96s (± 1.45%) 4.97s (± 1.59%) +0.01s (+ 0.26%) 4.85s 5.11s
Emit Time 3.27s (± 2.85%) 3.24s (± 2.16%) -0.02s (- 0.73%) 3.11s 3.36s
Total Time 10.23s (± 0.44%) 10.21s (± 0.39%) -0.02s (- 0.19%) 10.10s 10.31s
material-ui - node (v8.9.0, x64)
Memory used 453,097k (± 0.01%) 452,836k (± 0.01%) -261k (- 0.06%) 452,778k 452,967k
Parse Time 2.11s (± 0.50%) 2.11s (± 0.68%) +0.00s (+ 0.09%) 2.09s 2.16s
Bind Time 0.81s (± 0.45%) 0.80s (± 1.00%) -0.00s (- 0.50%) 0.79s 0.82s
Check Time 16.55s (± 0.79%) 16.52s (± 0.68%) -0.03s (- 0.17%) 16.27s 16.84s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 19.46s (± 0.69%) 19.43s (± 0.56%) -0.03s (- 0.15%) 19.19s 19.73s
Angular - node (v8.9.0, x86)
Memory used 185,671k (± 0.03%) 185,413k (± 0.02%) -258k (- 0.14%) 185,306k 185,521k
Parse Time 2.06s (± 0.85%) 2.05s (± 0.64%) -0.01s (- 0.39%) 2.02s 2.09s
Bind Time 1.08s (± 0.70%) 1.07s (± 0.62%) -0.01s (- 0.56%) 1.05s 1.08s
Check Time 5.01s (± 0.59%) 4.97s (± 0.47%) -0.03s (- 0.64%) 4.91s 5.03s
Emit Time 6.07s (± 1.04%) 6.02s (± 1.24%) -0.05s (- 0.82%) 5.79s 6.18s
Total Time 14.21s (± 0.59%) 14.12s (± 0.59%) -0.09s (- 0.65%) 13.84s 14.27s
Monaco - node (v8.9.0, x86)
Memory used 185,385k (± 0.02%) 185,384k (± 0.02%) -1k (- 0.00%) 185,300k 185,483k
Parse Time 1.60s (± 0.71%) 1.59s (± 0.76%) -0.00s (- 0.25%) 1.57s 1.63s
Bind Time 0.76s (± 0.58%) 0.77s (± 0.76%) +0.00s (+ 0.66%) 0.76s 0.78s
Check Time 5.40s (± 0.29%) 5.41s (± 0.39%) +0.01s (+ 0.09%) 5.36s 5.46s
Emit Time 2.88s (± 1.17%) 2.88s (± 0.81%) -0.00s (- 0.10%) 2.84s 2.95s
Total Time 10.65s (± 0.37%) 10.65s (± 0.26%) -0.00s (- 0.01%) 10.59s 10.72s
TFS - node (v8.9.0, x86)
Memory used 166,898k (± 0.01%) 166,914k (± 0.04%) +16k (+ 0.01%) 166,817k 167,090k
Parse Time 1.29s (± 0.58%) 1.29s (± 0.76%) +0.01s (+ 0.39%) 1.27s 1.31s
Bind Time 0.71s (± 0.48%) 0.71s (± 0.81%) +0.00s (+ 0.42%) 0.70s 0.72s
Check Time 4.64s (± 0.44%) 4.66s (± 0.67%) +0.03s (+ 0.60%) 4.61s 4.73s
Emit Time 3.00s (± 2.51%) 2.96s (± 0.81%) -0.05s (- 1.63%) 2.91s 3.01s
Total Time 9.64s (± 0.86%) 9.62s (± 0.30%) -0.01s (- 0.12%) 9.58s 9.70s
material-ui - node (v8.9.0, x86)
Memory used 256,547k (± 0.01%) 256,377k (± 0.01%) -170k (- 0.07%) 256,298k 256,476k
Parse Time 2.20s (± 0.79%) 2.18s (± 0.81%) -0.02s (- 0.68%) 2.15s 2.24s
Bind Time 0.69s (± 0.87%) 0.69s (± 1.66%) +0.00s (+ 0.29%) 0.67s 0.73s
Check Time 15.21s (± 0.56%) 15.14s (± 0.48%) -0.07s (- 0.44%) 14.91s 15.24s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 18.10s (± 0.51%) 18.02s (± 0.44%) -0.08s (- 0.44%) 17.75s 18.13s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-166-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v10.16.3, x64)
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
Scenarios
  • Angular - node (v10.16.3, x64)
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Monaco - node (v10.16.3, x64)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • TFS - node (v10.16.3, x64)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • material-ui - node (v10.16.3, x64)
  • material-ui - node (v12.1.0, x64)
  • material-ui - node (v8.9.0, x64)
  • material-ui - node (v8.9.0, x86)
Benchmark Name Iterations
Current 37827 10
Baseline master 10

@@ -7596,6 +7596,9 @@ namespace ts {
}
return anyType;
}
if (containsThisProperty(expression.left, expression.right)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
if (containsThisProperty(expression.left, expression.right)) {
if (containsSameNamedThisProperty(expression.left, expression.right)) {

(this PR is a bit drafty so I didn't take time to create good names)

@sandersn
Copy link
Member Author

sandersn commented Apr 8, 2020

Users tests don't change much -- there is evidence of better checking in chrome-devtools-frontend, and that's about it.

@sandersn
Copy link
Member Author

sandersn commented Apr 8, 2020

Of course, with no performance tests for JS, I'm not sure how to get good numbers there. Maybe I can manually compile chrome-devtools-frontend repeatedly.

@andrewbranch
Copy link
Member

  1. Does getInitializerTypeFromAssignmentDeclaration ever get called for TypeScript code?

  2. What happens when you have a self-referencing assignment that actually results in the wrong type? Do we allow it by short-circuiting to any?

    // @checkJs: true
    class C {
      constructor() {
        this.x = 0;
        this.x = this.x.toString();
      }
    }

    Does this PR make x remain number?

@sandersn
Copy link
Member Author

sandersn commented Apr 8, 2020

  1. Briefly: yes. The check thisProperty.expression.kind === SyntaxKind.ThisKeyword makes sure that the walk won't apply to TS.

Longer: It's only called from getWidenedTypeFromAssignmentDeclaration, which is called on BinaryExpressions from getTypeOfVariable[etal] and getTypeOfFunction[etal]. So it applies in TS to assignments like f.x = 1, but not this.x = 1 because the binder doesn't bind this-property assignments in TS.

  1. A self-referencing assignment results in an any, but it's then treated differently based on its location. In your example, the any is in the constructor with the number from this.x = 0, so the two are unioned, giving x: any. But if the self-assignment is in a method, then only the constructor type is used, and you get the correct error because x: number:
class C {
    constructor() {
        this.x = 0;
    }
    m() {
        this.x = this.x.toString();
    }
}

These are the same rules that operate normally so you'd get the same results for calling an any-typed function like this.x = mystery().

@@ -7596,6 +7596,9 @@ namespace ts {
}
return anyType;
}
if (containsThisProperty(expression.left, expression.right)) {
return anyType;
Copy link
Member

Choose a reason for hiding this comment

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

Rather than returning any on all binary expressions with both lhs and rhs references to the same thing, why not elide such binary expressions from the set of expressions whose types are union's together in the first place? This way the type is still calculated based on all other usages, and not quietly any'd?

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 a per-declaration type. The caller, getWidenedTypeFromAssignmentDeclaration, does something similar to what you suggest, except that it divides declarations into constructor vs non-constructor, then unions all types from the constructor if any exist, otherwise unioning all non-constructor types. See my previous reply to @andrewbranch's question.

I think this is better than dropping circular types because of Andrew's this.x = 0; this.x = this.x.toString() example. Ideally, we would give x: string, but I think x: any is better than x: number.

Copy link
Member

Choose a reason for hiding this comment

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

Right, but, like, if we never request the type of the declaration, it'll never get flagged as circular. Something like

                if (!jsdocType) {
                    (types || (types = [])).push(((isBinaryExpression(expression) && !containsSameNamedThisProperty(expression.left, expression.right)) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType);
                }

Copy link
Member

@weswigham weswigham Apr 8, 2020

Choose a reason for hiding this comment

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

I think this is better than dropping circular types because of Andrew's this.x = 0; this.x = this.x.toString() example. Ideally, we would give x: string, but I think x: any is better than x: number.

Moreover, we support

var x;
x = 1;
x = x.toString();
x;

without error, so why not

this.x = 1;
this.x = this.x.toString();
this.x;

if we find that case of value? We'd just need to assign the declared type to the autoType when we see a self-referential assignment like this.

Copy link
Member Author

@sandersn sandersn Apr 8, 2020

Choose a reason for hiding this comment

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

  1. Moving containsSameNamedThisProperty earlier is equivalent to returning never insteading of any. When you do this, though, you get this:
    /** @class */
    function C() {
        this.x = 0;
        this.x = this.x.toString();
        ~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.
    }

Which I don't think is good. I'd rather give x: any than x: number.

  1. I don't understand how you'd make this work with control flow's autoType, since I don't think it will work consistently across functions. Can you explain?

Copy link
Member

Choose a reason for hiding this comment

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

autoType is an any-like that's essentially narrowable by assignment in control flow. Just return it here instead of anyType and check out the behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm sceptical that it'll be helpful, but I'm biased by imagining a lot of short methods that would still end up with any. I filed #37900 to track this, since when I tried just return autoType, control flow didn't kick in and it's not obvious why.

@sandersn
Copy link
Member Author

sandersn commented Apr 8, 2020

Re performance: over 10 runs, the new code is 0.1 s faster to compile chrome-devtools-frontend than before, or 0.63%. I don't have significance measurements, but I think that's down to noise.

Because of the user test baselines change, I believe chrome-devtool exercises this code pretty well.

@sandersn
Copy link
Member Author

sandersn commented Apr 8, 2020

Tested performance when using isMatchingReference and it's 0.9% faster than master. Still probably noise.

@sandersn sandersn merged commit eb105ef into master Apr 10, 2020
@sandersn sandersn deleted the avoid-circular-reference-in-this-property-assignments branch April 10, 2020 23:41
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.

Instance property used in reassigning method causes implicit any in checkJs
4 participants