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

Add noUnnecessaryTypeAssertions compiler option, quickfix #51527

Closed

Conversation

weswigham
Copy link
Member

Fixes comments about this PR and allowing us to remove the no-unnecessary-type-assertion eslint rule (once we LKG and switch this on), since we can do it much more efficiently within the compiler. Unlike the eslint rule, this isn't configurable, but it is strongly based on our identity type relationship, rather than a bunch of heuristics using type flags and object identity. Using compiler internals also allows fixing some longstanding bugs with the lint rule, like the one that prompted this issue, without using heuristics.

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Nov 14, 2022
@weswigham weswigham force-pushed the no-unnessesary-assertions-quickfix branch from 7d58f8a to 1988e50 Compare November 14, 2022 20:51
const params = (source as ConditionalType).root.outerTypeParameters || [];
const sourceTypeArguments = map(params, t => (source as ConditionalType).mapper ? getMappedType(t, (source as ConditionalType).mapper!) : t);
const targetTypeArguments = map(params, t => (target as ConditionalType).mapper ? getMappedType(t, (target as ConditionalType).mapper!) : t);
return typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, map(params, () => VarianceFlags.Unmeasurable), /*reportErrors*/ false, IntersectionState.None);
Copy link
Member Author

Choose a reason for hiding this comment

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

So, this is kind of a drive-by speed-up for identity checking for conditional types. You'd think this change is unrelated, but since this change triggers identity comparisons in a new location, it discovered a bug!

Usually, identity checking is really fast, since we either bail quickly, or rapidly identify reference-equal types. However, we have an example in our test suite where we cast an expression which has a generative conditional type to what is essentially the same generative conditional type with an (allowable) type parameter substitution. Without this fix, it spun for... well, longer than I cared to wait. This, then, is a drive-by fix for a pathological case in conditional type identity checking I stumbled on. See tests\cases\compiler\conditionalTypeDoesntSpinForever.ts for the relevant test case. By comparing outer type parameter equality when conditional type roots match, we can shortcut pretty much all recursion, preventing the runaway generation and likely being more performant in the general case.

// if the expression type was widenable, we shouldn't assume the cast is extraneous.
// (Generally speaking, such casts are better served with `as const` casts nowadays though!)
else if (widenedType === exprType && isTypeIdenticalTo(targetType, widenedType)) {
errorOrSuggestion(!!compilerOptions.noUnnecessaryCasts, errNode, Diagnostics.Type_cast_has_no_effect_on_the_type_of_this_expression);
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 line is the core of the addition, and pretty much everything else in checker is to handle edge cases the eslint rule authors identified - like using a cast to prevent widening (a common occurrence before as const) or using a non-null assertion to prevent a use-before-assignment error on a non-nullable type'd variable.

@weswigham
Copy link
Member Author

@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 14, 2022

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

@jakebailey
Copy link
Member

Weird that the self check fails; the stack trace isn't log enough to show context but I'm wondering if there's a bug in the PR which only fires when compiling TS itself, something that isn't in a test case.

@weswigham
Copy link
Member Author

Odd that it doesn't repro locally, but I suspect I know the issue.

Copy link
Member

@DanielRosenwasser DanielRosenwasser left a comment

Choose a reason for hiding this comment

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

One general question is whether we need to be intentional about our terminology. Is this "noUnnecessaryCasts" or "noUnnecessaryTypeAssertions"? Is this about "unnecessary" casts/assertions, or "identical" casts/assertions?

@weswigham
Copy link
Member Author

weswigham commented Nov 14, 2022

@DanielRosenwasser as far as terminology goes, I mostly copied the language #32363 used for our existing await-removing quickfix, but s/await/type cast/. Only unique part is the compiler option, but the name kinda falls out from the wording used in the error.

@weswigham
Copy link
Member Author

Is this about "unnecessary" casts/assertions, or "identical" casts/assertions?

Having briefly looked at the eslint rule, identical is where we mostly are, but unnecessary is where we'd aspirationally be, which is why the name is as such in eslint, and also here. Like @RyanCavanaugh pointed out when we discussed it before, we could layer on more checks of situations where it's OK to delete the cast, like where the type being cast is already compatible with the contextual type (assuming there is one) and there's no inference to perform in said contextual type.

Comment on lines +32827 to +32828
// We need a fake reference that isn't parented to the nonnull expression, since the non-null will affect control flow's result
// by suppressing the initial return type at the end of the flow
Copy link
Member

@DanielRosenwasser DanielRosenwasser Nov 14, 2022

Choose a reason for hiding this comment

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

This is feels weird. Shouldn't that be a separate concern from CFA?

Copy link
Member Author

Choose a reason for hiding this comment

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

But not unprecedented! We do this for speculative/constructed control flow queries elsewhere already. See usages of getSyntheticElementAccess within the checker. I just couldn't reuse it because these aren't element accesses, they're just straight identifiers (sans assertion).

Copy link
Member Author

Choose a reason for hiding this comment

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

Shouldn't that be a separate concern from CFA?

🤷It's not, though. Last couple lines of getFlowTypeOfReference is basically "if we still have the initial type, under which contextual circumstances should we return the declared type instead", and those circumstances are basically what we wanna change here.

Copy link

Choose a reason for hiding this comment

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

@DanielRosenwasser This comment refers to a specific workaround required in the context of the control flow analysis (CFA) process. The aim here is to simulate a reference that doesn't originate from the nonnull expression to ensure that the control flow's result isn't affected solely by the presence of the non-null assertion. This workaround is reminiscent of similar approaches used in speculative or constructed control flow queries elsewhere in the codebase (like getSyntheticElementAccess within the checker). In this scenario, we're dealing with identifiers lacking assertions, and therefore, the approach isn't directly reusable. The concern addressed here is indeed entwined with the logic in the last few lines of getFlowTypeOfReference. Essentially, it delves into deciding, within certain contextual circumstances, when to return the declared type instead of retaining the initial type under which the analysis began. Hope this clarifies the context.

@weswigham
Copy link
Member Author

@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 14, 2022

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

Update: The results are in!

@weswigham weswigham changed the title Add noUnnecessaryCasts compiler option, quickfix Add noUnnecessaryTypeAssertions compiler option, quickfix Nov 14, 2022
@weswigham
Copy link
Member Author

@DanielRosenwasser I've tightened up the language across all the diagnostics and option name to only use the term type assertion rather than cast.

@typescript-bot
Copy link
Collaborator

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

Here they are:

Compiler

Comparison Report - main..51527
Metric main 51527 Delta Best Worst
Angular - node (v18.10.0, x64)
Memory used 341,214k (± 0.01%) 341,243k (± 0.01%) +29k (+ 0.01%) 341,143k 341,302k
Parse Time 1.57s (± 0.88%) 1.55s (± 0.85%) -0.02s (- 1.34%) 1.52s 1.58s
Bind Time 0.54s (± 0.92%) 0.53s (± 0.64%) -0.00s (- 0.56%) 0.53s 0.54s
Check Time 4.05s (± 0.79%) 4.04s (± 0.58%) -0.01s (- 0.20%) 4.00s 4.09s
Emit Time 4.27s (± 0.84%) 4.25s (± 0.84%) -0.02s (- 0.49%) 4.18s 4.33s
Total Time 10.43s (± 0.66%) 10.37s (± 0.42%) -0.05s (- 0.51%) 10.27s 10.49s
Compiler-Unions - node (v18.10.0, x64)
Memory used 188,819k (± 1.03%) 187,997k (± 1.10%) -822k (- 0.44%) 185,130k 190,824k
Parse Time 0.62s (± 1.07%) 0.61s (± 1.11%) -0.01s (- 0.81%) 0.60s 0.63s
Bind Time 0.33s (± 0.68%) 0.32s (± 1.05%) -0.01s (- 1.82%) 0.32s 0.33s
Check Time 5.01s (± 0.57%) 5.05s (± 0.51%) +0.04s (+ 0.78%) 4.99s 5.11s
Emit Time 1.55s (± 2.41%) 1.51s (± 0.80%) -0.04s (- 2.45%) 1.48s 1.54s
Total Time 7.51s (± 0.54%) 7.51s (± 0.35%) -0.01s (- 0.11%) 7.43s 7.55s
Monaco - node (v18.10.0, x64)
Memory used 320,524k (± 0.01%) 320,597k (± 0.02%) +73k (+ 0.02%) 320,439k 320,701k
Parse Time 1.18s (± 1.38%) 1.17s (± 1.47%) -0.01s (- 1.02%) 1.14s 1.22s
Bind Time 0.49s (± 1.91%) 0.48s (± 1.71%) -0.01s (- 1.83%) 0.47s 0.50s
Check Time 3.88s (± 0.60%) 3.87s (± 0.66%) -0.01s (- 0.18%) 3.83s 3.94s
Emit Time 2.28s (± 1.00%) 2.25s (± 0.84%) -0.03s (- 1.36%) 2.21s 2.30s
Total Time 7.83s (± 0.50%) 7.77s (± 0.61%) -0.06s (- 0.79%) 7.68s 7.88s
TFS - node (v18.10.0, x64)
Memory used 283,538k (± 0.24%) 283,601k (± 0.23%) +64k (+ 0.02%) 282,986k 284,986k
Parse Time 0.97s (± 1.07%) 0.95s (± 0.86%) -0.01s (- 1.55%) 0.94s 0.98s
Bind Time 0.45s (± 6.14%) 0.45s (± 6.35%) -0.00s (- 0.22%) 0.42s 0.56s
Check Time 3.80s (± 0.59%) 3.81s (± 0.50%) +0.00s (+ 0.05%) 3.78s 3.87s
Emit Time 2.22s (± 0.85%) 2.19s (± 1.08%) -0.02s (- 1.13%) 2.15s 2.25s
Total Time 7.44s (± 0.72%) 7.40s (± 0.45%) -0.04s (- 0.52%) 7.33s 7.47s
material-ui - node (v18.10.0, x64)
Memory used 436,031k (± 0.02%) 436,012k (± 0.01%) -19k (- 0.00%) 435,916k 436,215k
Parse Time 1.35s (± 0.78%) 1.33s (± 0.55%) -0.02s (- 1.33%) 1.32s 1.35s
Bind Time 0.50s (± 0.73%) 0.49s (± 0.68%) -0.01s (- 1.21%) 0.48s 0.50s
Check Time 10.40s (± 0.56%) 10.28s (± 0.82%) -0.12s (- 1.13%) 10.14s 10.51s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 12.25s (± 0.52%) 12.11s (± 0.71%) -0.14s (- 1.15%) 11.97s 12.35s
xstate - node (v18.10.0, x64)
Memory used 518,685k (± 0.01%) 518,864k (± 0.01%) +179k (+ 0.03%) 518,751k 518,996k
Parse Time 1.94s (± 0.31%) 1.91s (± 0.55%) -0.03s (- 1.54%) 1.89s 1.93s
Bind Time 0.79s (± 4.16%) 0.78s (± 4.04%) -0.01s (- 1.01%) 0.70s 0.82s
Check Time 1.06s (± 0.96%) 1.04s (± 0.64%) -0.01s (- 1.42%) 1.03s 1.06s
Emit Time 0.05s (± 0.00%) 0.05s (± 0.00%) 0.00s ( 0.00%) 0.05s 0.05s
Total Time 3.84s (± 0.92%) 3.79s (± 0.92%) -0.05s (- 1.41%) 3.69s 3.83s
Angular - node (v16.17.1, x64)
Memory used 340,522k (± 0.02%) 340,604k (± 0.02%) +82k (+ 0.02%) 340,407k 340,687k
Parse Time 1.88s (± 0.66%) 1.87s (± 0.71%) -0.01s (- 0.53%) 1.84s 1.91s
Bind Time 0.65s (± 0.89%) 0.65s (± 0.58%) -0.01s (- 1.23%) 0.64s 0.65s
Check Time 5.16s (± 0.38%) 5.23s (± 0.89%) +0.06s (+ 1.24%) 5.15s 5.38s
Emit Time 5.11s (± 0.76%) 5.09s (± 1.13%) -0.02s (- 0.41%) 4.98s 5.23s
Total Time 12.81s (± 0.46%) 12.84s (± 0.56%) +0.03s (+ 0.23%) 12.70s 13.03s
Compiler-Unions - node (v16.17.1, x64)
Memory used 188,257k (± 0.65%) 187,528k (± 0.53%) -729k (- 0.39%) 186,767k 190,263k
Parse Time 0.79s (± 0.75%) 0.79s (± 0.92%) -0.01s (- 0.88%) 0.77s 0.80s
Bind Time 0.42s (± 0.53%) 0.42s (± 0.82%) -0.00s (- 0.95%) 0.41s 0.42s
Check Time 6.05s (± 0.72%) 6.09s (± 0.38%) +0.04s (+ 0.61%) 6.04s 6.16s
Emit Time 1.91s (± 0.72%) 1.89s (± 0.85%) -0.01s (- 0.79%) 1.86s 1.92s
Total Time 9.17s (± 0.47%) 9.19s (± 0.43%) +0.01s (+ 0.13%) 9.10s 9.29s
Monaco - node (v16.17.1, x64)
Memory used 319,866k (± 0.01%) 319,958k (± 0.01%) +92k (+ 0.03%) 319,900k 320,022k
Parse Time 1.43s (± 0.73%) 1.42s (± 0.49%) -0.01s (- 0.63%) 1.40s 1.43s
Bind Time 0.59s (± 1.00%) 0.59s (± 0.58%) -0.01s (- 1.18%) 0.58s 0.59s
Check Time 4.87s (± 0.39%) 4.94s (± 0.55%) +0.07s (+ 1.40%) 4.87s 4.99s
Emit Time 2.72s (± 0.90%) 2.72s (± 0.67%) +0.01s (+ 0.22%) 2.67s 2.76s
Total Time 9.61s (± 0.40%) 9.67s (± 0.42%) +0.06s (+ 0.59%) 9.56s 9.73s
TFS - node (v16.17.1, x64)
Memory used 282,300k (± 0.01%) 282,393k (± 0.02%) +93k (+ 0.03%) 282,203k 282,474k
Parse Time 1.17s (± 0.51%) 1.15s (± 0.77%) -0.01s (- 1.03%) 1.14s 1.17s
Bind Time 0.65s (± 3.63%) 0.65s (± 3.90%) -0.01s (- 1.23%) 0.59s 0.69s
Check Time 4.76s (± 0.39%) 4.81s (± 0.48%) +0.05s (+ 1.05%) 4.74s 4.86s
Emit Time 2.76s (± 2.01%) 2.71s (± 1.39%) -0.05s (- 1.77%) 2.66s 2.81s
Total Time 9.35s (± 0.72%) 9.32s (± 0.49%) -0.03s (- 0.28%) 9.25s 9.48s
material-ui - node (v16.17.1, x64)
Memory used 435,297k (± 0.01%) 435,353k (± 0.01%) +56k (+ 0.01%) 435,291k 435,521k
Parse Time 1.65s (± 0.50%) 1.64s (± 0.61%) -0.02s (- 1.03%) 1.62s 1.66s
Bind Time 0.50s (± 0.98%) 0.50s (± 1.15%) -0.00s (- 0.79%) 0.49s 0.51s
Check Time 11.96s (± 0.42%) 11.92s (± 1.44%) -0.05s (- 0.40%) 11.67s 12.52s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 14.13s (± 0.36%) 14.06s (± 1.26%) -0.07s (- 0.48%) 13.79s 14.67s
xstate - node (v16.17.1, x64)
Memory used 516,263k (± 0.01%) 516,537k (± 0.01%) +273k (+ 0.05%) 516,436k 516,688k
Parse Time 2.33s (± 0.45%) 2.31s (± 0.29%) -0.02s (- 0.86%) 2.30s 2.33s
Bind Time 0.84s (± 1.90%) 0.84s (± 0.77%) -0.01s (- 0.71%) 0.82s 0.85s
Check Time 1.36s (± 0.50%) 1.38s (± 0.71%) +0.02s (+ 1.62%) 1.36s 1.40s
Emit Time 0.06s (± 0.00%) 0.06s (± 0.00%) 0.00s ( 0.00%) 0.06s 0.06s
Total Time 4.60s (± 0.44%) 4.59s (± 0.36%) -0.01s (- 0.17%) 4.56s 4.63s
Angular - node (v14.15.1, x64)
Memory used 334,041k (± 0.01%) 334,052k (± 0.00%) +12k (+ 0.00%) 334,031k 334,103k
Parse Time 2.08s (± 0.55%) 2.04s (± 0.32%) -0.04s (- 1.73%) 2.03s 2.06s
Bind Time 0.70s (± 0.53%) 0.70s (± 0.71%) -0.01s (- 1.28%) 0.69s 0.71s
Check Time 5.52s (± 0.33%) 5.59s (± 0.60%) +0.06s (+ 1.16%) 5.50s 5.67s
Emit Time 5.22s (± 0.68%) 5.17s (± 0.48%) -0.05s (- 0.92%) 5.12s 5.24s
Total Time 13.53s (± 0.38%) 13.49s (± 0.32%) -0.03s (- 0.22%) 13.42s 13.63s
Compiler-Unions - node (v14.15.1, x64)
Memory used 181,579k (± 0.01%) 182,246k (± 0.42%) +667k (+ 0.37%) 181,872k 185,316k
Parse Time 0.90s (± 0.62%) 0.89s (± 0.42%) -0.01s (- 1.45%) 0.88s 0.89s
Bind Time 0.46s (± 0.48%) 0.45s (± 1.09%) -0.01s (- 1.52%) 0.45s 0.47s
Check Time 6.36s (± 0.45%) 6.38s (± 0.36%) +0.01s (+ 0.22%) 6.34s 6.42s
Emit Time 2.04s (± 0.71%) 2.02s (± 0.75%) -0.02s (- 1.13%) 2.00s 2.06s
Total Time 9.77s (± 0.35%) 9.74s (± 0.25%) -0.03s (- 0.32%) 9.68s 9.79s
Monaco - node (v14.15.1, x64)
Memory used 314,689k (± 0.05%) 314,695k (± 0.01%) +6k (+ 0.00%) 314,626k 314,743k
Parse Time 1.58s (± 0.66%) 1.57s (± 0.45%) -0.01s (- 0.70%) 1.55s 1.58s
Bind Time 0.64s (± 0.78%) 0.63s (± 0.47%) -0.00s (- 0.47%) 0.63s 0.64s
Check Time 5.21s (± 0.52%) 5.23s (± 0.38%) +0.03s (+ 0.48%) 5.19s 5.28s
Emit Time 2.92s (± 0.69%) 2.86s (± 0.38%) -0.06s (- 2.12%) 2.84s 2.88s
Total Time 10.34s (± 0.41%) 10.29s (± 0.24%) -0.05s (- 0.48%) 10.23s 10.34s
TFS - node (v14.15.1, x64)
Memory used 279,310k (± 0.01%) 279,351k (± 0.01%) +41k (+ 0.01%) 279,299k 279,409k
Parse Time 1.34s (± 0.62%) 1.33s (± 1.18%) -0.02s (- 1.26%) 1.30s 1.37s
Bind Time 0.59s (± 0.80%) 0.59s (± 0.56%) 0.00s ( 0.00%) 0.58s 0.60s
Check Time 5.10s (± 0.36%) 5.10s (± 0.36%) -0.00s (- 0.08%) 5.05s 5.12s
Emit Time 3.08s (± 0.92%) 3.06s (± 0.65%) -0.02s (- 0.71%) 3.03s 3.11s
Total Time 10.12s (± 0.34%) 10.08s (± 0.32%) -0.05s (- 0.44%) 9.99s 10.15s
material-ui - node (v14.15.1, x64)
Memory used 430,756k (± 0.01%) 430,747k (± 0.01%) -10k (- 0.00%) 430,667k 430,834k
Parse Time 1.88s (± 0.46%) 1.87s (± 0.52%) -0.01s (- 0.69%) 1.85s 1.89s
Bind Time 0.54s (± 0.69%) 0.53s (± 0.56%) -0.00s (- 0.56%) 0.53s 0.54s
Check Time 12.38s (± 0.70%) 12.23s (± 0.32%) -0.15s (- 1.20%) 12.15s 12.33s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 14.79s (± 0.60%) 14.63s (± 0.27%) -0.17s (- 1.13%) 14.54s 14.72s
xstate - node (v14.15.1, x64)
Memory used 504,439k (± 0.00%) 504,653k (± 0.00%) +215k (+ 0.04%) 504,616k 504,689k
Parse Time 2.65s (± 0.70%) 2.62s (± 0.68%) -0.03s (- 1.24%) 2.58s 2.67s
Bind Time 0.86s (± 0.97%) 0.84s (± 0.88%) -0.02s (- 1.75%) 0.83s 0.86s
Check Time 1.49s (± 0.35%) 1.48s (± 0.54%) -0.01s (- 0.47%) 1.47s 1.50s
Emit Time 0.07s (± 0.00%) 0.07s (± 0.00%) 0.00s ( 0.00%) 0.07s 0.07s
Total Time 5.07s (± 0.30%) 5.01s (± 0.38%) -0.06s (- 1.10%) 4.97s 5.07s
System
Machine Namets-ci-ubuntu
Platformlinux 5.4.0-131-generic
Architecturex64
Available Memory16 GB
Available Memory15 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v18.10.0, x64)
  • node (v16.17.1, x64)
  • node (v14.15.1, x64)
Scenarios
  • Angular - node (v18.10.0, x64)
  • Angular - node (v16.17.1, x64)
  • Angular - node (v14.15.1, x64)
  • Compiler-Unions - node (v18.10.0, x64)
  • Compiler-Unions - node (v16.17.1, x64)
  • Compiler-Unions - node (v14.15.1, x64)
  • Monaco - node (v18.10.0, x64)
  • Monaco - node (v16.17.1, x64)
  • Monaco - node (v14.15.1, x64)
  • TFS - node (v18.10.0, x64)
  • TFS - node (v16.17.1, x64)
  • TFS - node (v14.15.1, x64)
  • material-ui - node (v18.10.0, x64)
  • material-ui - node (v16.17.1, x64)
  • material-ui - node (v14.15.1, x64)
  • xstate - node (v18.10.0, x64)
  • xstate - node (v16.17.1, x64)
  • xstate - node (v14.15.1, x64)
Benchmark Name Iterations
Current 51527 10
Baseline main 10

TSServer

Comparison Report - main..51527
Metric main 51527 Delta Best Worst
Compiler-UnionsTSServer - node (v18.10.0, x64)
Req 1 - updateOpen 1,047ms (± 0.50%) 1,042ms (± 0.92%) -4ms (- 0.40%) 1,019ms 1,067ms
Req 2 - geterr 2,568ms (± 0.75%) 2,566ms (± 0.38%) -2ms (- 0.06%) 2,546ms 2,584ms
Req 3 - references 166ms (± 0.99%) 163ms (± 0.36%) -3ms (- 1.69%) 162ms 164ms
Req 4 - navto 138ms (± 0.94%) 138ms (± 0.73%) +0ms (+ 0.07%) 136ms 140ms
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) 0 ( 0.00%) 1,356 1,356
Req 5 - completionInfo 62ms (± 2.71%) 62ms (± 2.03%) 0ms ( 0.00%) 57ms 63ms
CompilerTSServer - node (v18.10.0, x64)
Req 1 - updateOpen 1,102ms (± 0.50%) 1,093ms (± 0.35%) -9ms (- 0.83%) 1,082ms 1,101ms
Req 2 - geterr 1,573ms (± 0.39%) 1,576ms (± 0.67%) +4ms (+ 0.22%) 1,544ms 1,592ms
Req 3 - references 171ms (± 0.98%) 177ms (± 5.49%) +6ms (+ 3.21%) 165ms 198ms
Req 4 - navto 151ms (± 0.88%) 157ms (± 7.53%) +6ms (+ 3.98%) 150ms 204ms
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) 0 ( 0.00%) 1,518 1,518
Req 5 - completionInfo 53ms (± 1.46%) 54ms (± 4.91%) +1ms (+ 1.51%) 49ms 61ms
xstateTSServer - node (v18.10.0, x64)
Req 1 - updateOpen 1,518ms (± 0.76%) 1,501ms (± 0.46%) -16ms (- 1.08%) 1,491ms 1,519ms
Req 2 - geterr 562ms (± 0.53%) 554ms (± 0.53%) -8ms (- 1.37%) 550ms 565ms
Req 3 - references 59ms (± 2.01%) 58ms (± 1.55%) -2ms (- 2.70%) 57ms 61ms
Req 4 - navto 197ms (± 1.03%) 195ms (± 0.82%) -3ms (- 1.27%) 192ms 199ms
Req 5 - completionInfo count 3,151 (± 0.00%) 3,151 (± 0.00%) 0 ( 0.00%) 3,151 3,151
Req 5 - completionInfo 212ms (± 1.32%) 211ms (± 1.26%) -1ms (- 0.38%) 204ms 217ms
Compiler-UnionsTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 1,303ms (± 0.79%) 1,302ms (± 0.30%) -1ms (- 0.08%) 1,292ms 1,308ms
Req 2 - geterr 3,153ms (± 0.65%) 3,199ms (± 0.82%) +46ms (+ 1.44%) 3,126ms 3,257ms
Req 3 - references 193ms (± 1.37%) 191ms (± 0.63%) -3ms (- 1.30%) 188ms 194ms
Req 4 - navto 151ms (± 0.70%) 153ms (± 0.34%) +2ms (+ 1.39%) 152ms 154ms
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) 0 ( 0.00%) 1,356 1,356
Req 5 - completionInfo 59ms (± 2.55%) 70ms (± 9.98%) +12ms (+19.66%) 58ms 94ms
CompilerTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 1,394ms (± 0.70%) 1,380ms (± 0.91%) -15ms (- 1.06%) 1,353ms 1,408ms
Req 2 - geterr 2,072ms (± 0.44%) 2,080ms (± 0.69%) +9ms (+ 0.42%) 2,048ms 2,109ms
Req 3 - references 199ms (± 0.56%) 198ms (± 0.86%) -2ms (- 0.85%) 194ms 201ms
Req 4 - navto 165ms (± 0.62%) 165ms (± 0.85%) +0ms (+ 0.18%) 163ms 168ms
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) 0 ( 0.00%) 1,518 1,518
Req 5 - completionInfo 58ms (± 2.78%) 57ms (± 2.70%) -1ms (- 1.22%) 54ms 60ms
xstateTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 1,819ms (± 0.36%) 1,820ms (± 0.64%) +1ms (+ 0.07%) 1,800ms 1,845ms
Req 2 - geterr 713ms (± 0.54%) 718ms (± 0.66%) +4ms (+ 0.62%) 710ms 730ms
Req 3 - references 69ms (± 1.39%) 68ms (± 0.70%) -1ms (- 1.17%) 66ms 68ms
Req 4 - navto 197ms (± 1.11%) 198ms (± 0.80%) +1ms (+ 0.46%) 193ms 202ms
Req 5 - completionInfo count 3,151 (± 0.00%) 3,151 (± 0.00%) 0 ( 0.00%) 3,151 3,151
Req 5 - completionInfo 251ms (± 0.95%) 254ms (± 0.78%) +3ms (+ 1.07%) 251ms 260ms
Compiler-UnionsTSServer - node (v14.15.1, x64)
Req 1 - updateOpen 1,461ms (± 0.44%) 1,441ms (± 0.35%) -20ms (- 1.40%) 1,427ms 1,453ms
Req 2 - geterr 3,426ms (± 0.65%) 3,426ms (± 0.55%) +0ms (+ 0.00%) 3,373ms 3,465ms
Req 3 - references 206ms (± 0.55%) 204ms (± 1.20%) -2ms (- 0.92%) 200ms 209ms
Req 4 - navto 162ms (± 0.90%) 162ms (± 0.57%) -0ms (- 0.12%) 160ms 164ms
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) 0 ( 0.00%) 1,356 1,356
Req 5 - completionInfo 66ms (± 6.92%) 59ms (± 3.25%) 🟩-7ms (-10.93%) 56ms 62ms
CompilerTSServer - node (v14.15.1, x64)
Req 1 - updateOpen 1,530ms (± 0.58%) 1,522ms (± 0.38%) -8ms (- 0.54%) 1,512ms 1,537ms
Req 2 - geterr 2,262ms (± 0.29%) 2,255ms (± 0.22%) -7ms (- 0.32%) 2,242ms 2,262ms
Req 3 - references 213ms (± 1.28%) 209ms (± 1.02%) -5ms (- 2.11%) 204ms 213ms
Req 4 - navto 174ms (± 0.77%) 173ms (± 0.57%) -1ms (- 0.69%) 172ms 176ms
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) 0 ( 0.00%) 1,518 1,518
Req 5 - completionInfo 61ms (± 7.63%) 56ms (± 1.39%) 🟩-5ms (- 8.66%) 55ms 58ms
xstateTSServer - node (v14.15.1, x64)
Req 1 - updateOpen 2,037ms (± 0.62%) 2,008ms (± 0.48%) -29ms (- 1.43%) 1,978ms 2,028ms
Req 2 - geterr 751ms (± 0.58%) 741ms (± 0.54%) -10ms (- 1.32%) 735ms 750ms
Req 3 - references 73ms (± 1.16%) 72ms (± 1.93%) -1ms (- 1.37%) 69ms 76ms
Req 4 - navto 222ms (± 0.64%) 218ms (± 0.55%) -4ms (- 1.98%) 215ms 220ms
Req 5 - completionInfo count 3,151 (± 0.00%) 3,151 (± 0.00%) 0 ( 0.00%) 3,151 3,151
Req 5 - completionInfo 270ms (± 0.57%) 272ms (± 1.89%) +3ms (+ 1.00%) 266ms 283ms
System
Machine Namets-ci-ubuntu
Platformlinux 5.4.0-131-generic
Architecturex64
Available Memory16 GB
Available Memory15 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v18.10.0, x64)
  • node (v16.17.1, x64)
  • node (v14.15.1, x64)
Scenarios
  • Compiler-UnionsTSServer - node (v18.10.0, x64)
  • Compiler-UnionsTSServer - node (v16.17.1, x64)
  • Compiler-UnionsTSServer - node (v14.15.1, x64)
  • CompilerTSServer - node (v18.10.0, x64)
  • CompilerTSServer - node (v16.17.1, x64)
  • CompilerTSServer - node (v14.15.1, x64)
  • xstateTSServer - node (v18.10.0, x64)
  • xstateTSServer - node (v16.17.1, x64)
  • xstateTSServer - node (v14.15.1, x64)
Benchmark Name Iterations
Current 51527 10
Baseline main 10

Startup

Comparison Report - main..51527
Metric main 51527 Delta Best Worst
tsc-startup - node (v16.17.1, x64)
Execution time 120.22ms (± 0.53%) 117.95ms (± 0.40%) -2.27ms (- 1.89%) 115.49ms 129.20ms
tsserver-startup - node (v16.17.1, x64)
Execution time 201.76ms (± 0.47%) 198.80ms (± 0.35%) -2.96ms (- 1.47%) 195.76ms 212.01ms
tsserverlibrary-startup - node (v16.17.1, x64)
Execution time 194.36ms (± 0.46%) 191.93ms (± 0.30%) -2.43ms (- 1.25%) 189.05ms 199.91ms
typescript-startup - node (v16.17.1, x64)
Execution time 178.93ms (± 0.45%) 177.26ms (± 0.33%) -1.67ms (- 0.93%) 174.58ms 184.55ms
System
Machine Namets-ci-ubuntu
Platformlinux 5.4.0-131-generic
Architecturex64
Available Memory16 GB
Available Memory15 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v16.17.1, x64)
Scenarios
  • tsc-startup - node (v16.17.1, x64)
  • tsserver-startup - node (v16.17.1, x64)
  • tsserverlibrary-startup - node (v16.17.1, x64)
  • typescript-startup - node (v16.17.1, x64)
Benchmark Name Iterations
Current 51527 10
Baseline main 10

Developer Information:

Download Benchmark

@jakebailey
Copy link
Member

I really like this, but I don't know if we decided to add it in the design meeting or not (given it's another knob).

@weswigham
Copy link
Member Author

It all functions without the compiler option, so that much I think can be unambiguously helpful, given the quick fix - the option just enables reporting it as an error (and thus be a true replacement for the lint rule).

@fatcerberus
Copy link

using a non-null assertion to prevent a use-before-assignment error

I'm not sure why I thought this but I always assumed this was one of the intended use cases for non-null assertions. Pretty sure I've used them once or twice for this exact purpose. Maybe control flow analysis has gotten good enough that this is never necessary in real code anymore, though.

@jakebailey
Copy link
Member

Also cc @bradzacher and @JoshuaKGoldberg for interest.

category: Diagnostics.Type_Checking,
description: Diagnostics.Raise_an_error_when_a_type_assertion_does_not_affect_the_type_of_an_expression,
defaultValueDescription: false,
},
Copy link
Contributor

@JoshuaKGoldberg JoshuaKGoldberg Nov 16, 2022

Choose a reason for hiding this comment

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

Oooh boy. The last I remember hearing of "linting" style features in TypeScript around noUnusedLocals and noUnusedParameters was that the team was strongly considering moving away from them? (this comment should not be treated as a source - I'm asking about anecdotal memories)

As much as I love making this a feature, we've already come into conflict with the noUnused* flags in lint land with https://typescript-eslint.io/rules/no-unused-vars. Users often want more flexibility than TypeScript can offer with just compiler options. See https://eslint.org/docs/latest/rules/no-unused-vars#options for options demanded by enough users to be justified adding to ESLint core, and typescript-eslint/typescript-eslint#5271 for a reference on many of the wacky ways users have asked to be able to configure rules.

Is there more context that can be provided on the TypeScript team's opinion on adding more compiler options for lint-area responsibilities? Perhaps an alternate long-term strategy would be to provide an API we can hook into? Perhaps even a ... type relationship API? 😁

Copy link
Member

Choose a reason for hiding this comment

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

My impetus for suggesting this was #51456, where I was trying to stop type checking during lint as doing so is half of our lint time, which is not so good for editor responsiveness in the project (for those of us who run ESLint in the editor; I think I may be one of the only ones on the team willing to take the perf hit of running it as an extension on this repo).

We only wanted this one rule, which is something that TS could be doing internally pretty easily.

But, I'm not really the one to be answering for the whole team's opinion here.

Copy link
Contributor

Choose a reason for hiding this comment

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

If linting with type info is slow for you guys, perhaps we can work together to figure out how we can improve the performance overall?

Obviously we won't be able to get it to the same speed as non-type-aware lint, but perhaps we can close that gap. This would ofc be a great win for the entire community!

Copy link
Member

Choose a reason for hiding this comment

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

I'd love to come up with ways to do so, though given the time is roughly the time it takes to compile TS itself, it's hard to know what could be improved besides improving the performance of TS itself. (e.g., upgrade to 5.0 😄)

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 mean, it's basically slow because it's duplicating all the work the compiler already does in the language service to fetch errors in a file for the editor, or at least a large part of that work, because calculating the types at a bunch of locations is most of the work the compiler does. I also don't think the eslint editor extension even uses our language service APIs to keep around a persistent project, so it's also not incremental in its parse behavior as far as the TS API is concerned (even though normal lints may be incremental).

Honestly, I'd still love to see linter plugins that could be integrated straight into the compiler at some point, like I implemented back when I was an intern - being able to guarantee a single walk that's incremental, shared across all rules, and reuses checker data where possible would be big cross-project upside, we just are still skittish about a built-in plug-in model after all this time because we're scared both about untrusted code in the editor (maybe passable now that the editor asks if you trust the code) and about being blamed for the performance penalty poorly written plugins may cause (since perf is such a huge concern).

Copy link
Member Author

Choose a reason for hiding this comment

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

Is there more context that can be provided on the TypeScript team's opinion on adding more compiler options for lint-area responsibilities?

What we've said on this, specifically, in our discussions at this point is that while we prefer things like this to be external lint rules, if there's outsized cost or capability lost by them being external (eg, because they require fancy control flow checks to properly implement), we're more OK with them living in the compiler for now. So current internal consensus is mostly that stuff like noFallthroughInSwitch really should have been external, since it's a fast syntactic check, noUnusedX is a bit more middling since it's kinda free for us to track while already checking, and stuff like this that needs control flow based definite assignment analysis and relationship checks to answer right is right up the alley of what our quickfixes should pick up, since we're very well-positioned to answer this question quickly during a normal type check.

Sans the compiler option to make this into an error, this check & quick fix is almost a carbon copy of our unneeded await quickfix, so we've had similar functionally knocking around for awhile.

Copy link
Contributor

Choose a reason for hiding this comment

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

eslint editor extension even uses our language service APIs

cc @bradzacher - is this something that ESLint today could support?

Most of typescript-eslint is pluggable and supports receiving TypeScript programs. For example, the parser's options can include a program: https://github.com/typescript-eslint/typescript-eslint/blob/07119945fb0c1d6acb421b1b9ba972c8d9c8942a/packages/types/src/parser-options.ts#L53.

This lack of language service / program reuse is one of our biggest pain points. We added an FAQ about out-of-date type info to our site. eslint/eslint#16557 (comment) has context on the ESLint structural side.

linter plugins that could be integrated straight into the compiler at some point

+1. https://github.com/Quramy/typescript-eslint-language-service is the closest I've seen used in the wild.

outsized cost or capability lost by them being external

Separate from the performance concerns, no-unnecessary-type-assertion is one of the less costly rules in TypeScript-ESLint core. It's only a couple hundred lines of real logic. Searching for accepted no-unnecessary-type-assertion issues shows only 22 open since the beginning of 2019. Only 13 of those are bugs. I'd put forward that the maintenance cost on our side for it is relatively quite small.

Copy link
Contributor

Choose a reason for hiding this comment

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

is this something that ESLint today could support?

No, we don't know when we're running in the IDE, so we can't spin up a server.

But we default to using a ts.WatchCompilerHostOfConfigFile (and by extension a ts.BuilderProgram), so we are doing incremental builds using persistent programs.
This is also cached forever and reused whenever a file that is included in the program is linted.

@sandersn
Copy link
Member

sandersn commented Dec 5, 2023

My memory is that we discussed this in a design meeting and decided not to take it. But I don't see any links to notes here. @weswigham or @DanielRosenwasser do you remember whether we decided anything about this?

My feeling on this is basically the same as @JoshuaKGoldberg 's comment: #51527 (comment)

And in fact we've got a PR for exposing isTypeAssignableTo now: #56448

@weswigham weswigham added the Experiment A fork with an experimental idea which might not make it into master label Aug 13, 2024
@weswigham weswigham closed this Aug 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team Experiment A fork with an experimental idea which might not make it into master For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

9 participants