Skip to content

Apply (Un)Capitalize template string mappings on generic template literal types in a safe manner #52112

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

@Andarist Andarist commented Jan 5, 2023

fixes #52102

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Jan 5, 2023
@devanshj
Copy link

devanshj commented Jan 11, 2023

The problem is not with the mapping being applied to the generic template literals but with the mapping logic of Capitalize and Uncapitalize for template literals itself.

According to applyTemplateStringMapping, Uncaptalize<`${A}${B}`> is same as `${Uncapitalize<A>}${B}` (same for Capitalize) but this is not true if A happens to be "" which is exactly what happens in the linked issue. To give an concrete example, Uncapitalize<`${""}${"Abc"}`> is "abc", whereas `${Uncapitalize<"">}${"Abc"}` is "Abc".

So Lowercase and Uppercase mapping can be applied to generic template literals but not Capitalize and Uncapitalize.

Edit: Or actually even Capitalize and Uncapitalize can be applied as long as texts[0] !== "". For example Uncapitalize<`A${X}{Y}b${Z}`> can be simplified to `a${X}${Y}b${Z}`. So the new logic would look something like this...

// pass the type instead of texts and types
const { texts, types } = type
// ...
case IntrinsicTypeKind.Capitalize:
  return (
    texts[0] === "" ? type :
    getTemplateLiteralType([texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], types)
  )

case IntrinsicTypeKind.Uncapitalize:
  // similar change

@Andarist
Copy link
Contributor Author

Ye, I also thought about applying the fix in those mapping functions - I just came to the conclusion that this fix is potentially easier and that it automatically makes it safer to introduce new mappings as this is an edge case that is easy to forget. I'm happy to adjust the fix if requested though.

@devanshj
Copy link

But regardless of whether we simplify generic template literals or not, the mapping logic of Capitalize and Uncapitalize requires a fix because it's incorrect. But yeah I'll let Anders chime in.

@Andarist
Copy link
Contributor Author

When that would manifest itself outside of generic contexts though?

@devanshj
Copy link

devanshj commented Jan 12, 2023

If there's a logical error in the code it should be fixed whether or not the control will reach that branch. Come on Mateusz, I'm pretty sure you know all this ;P

But to answer your question, yes today a mapping would never apply to a non-generic template literal, because all non-generic template literals immediately simplify to string literals.

But "today" is key here, it's possible this is no longer true in future. Eg in future it's possible that we support things like this...

type Digit = "0" | "1" | "2" | ... | "9"
type Digits = `${Digit}${"" | Digits}`

Here the template literal is not generic as it has no non-generic types and yet it can't be simplified to a string literal. So those mappings can be applied to such non-generic template literals which can lead to a bug because of the incorrect logic in them.

I can give you an even more simple example though: The simplification of non-generic template literals into string literals that happens today is not a lot more than an optimization, it can be pulled anytime in future, ie `${""}{"Abc"}` no longer simplifies to a string literal "Abc", we decide to keep it as it as and here if we apply those mappings on it, it'd lead to a bug because they have incorrect logic.

Again all this is just to answer your question, this is not the reason to fix the logical error, the reason to fix the logical error is that it's a logical error.

… safe way for (Un)Capitalize

Co-authored-by: Devansh Jethmalani <[email protected]>
@Andarist
Copy link
Contributor Author

IMHO, if we can't write a test case that validates a change today - then we shouldn't introduce a change. We can't predict the future and it makes sense to write the code with the current invariants of the behavior in mind. Code grows and evolves over time - I often remove parts of the code and rerun tests to learn why the given code is needed and how the system works, so the ideal situation, for me, is that some tests fail after removing any given line/condition/whatever. If the tests don't fail - that is a strong signal to me that the code is redundant and that it can be removed.

That being said - I came to the conclusion that I like the idea of reducing/simplifying as much as possible~ upfront. I've applied your suggestion, thanks!

@Andarist Andarist changed the title Dont apply template string mappings on generic template literal types Apply (Un)Capitalize template string mappings on generic template literal types in a safe manner Jan 14, 2023
@devanshj
Copy link

devanshj commented Jan 15, 2023

IMHO, if we can't write a test case that validates a change today - then we shouldn't introduce a change.

There's a bit of a confusion here, we're comparing my change against yours, not my change on top of yours against yours. My change is very much testable, the linked issue being a failing test which would then pass with the change.

We can't predict the future and it makes sense to write the code with the current invariants of the behavior in mind.

Aside the fact I don't fully agree with this and without going into why, this is not in contradiction with what I said because I never said the fix is for the future or something, I said...

Again all this is just to answer your question, this is not the reason to fix the logical error, the reason to fix the logical error is that it's a logical error.

I think we've deviated way too much, the reason for the linked bug was the logical error in Uncapitalize and Capitalize, your change didn't fix it at all, it just changed the code so that the logical error never surfaces whilst still being there, which makes it a bad fix, but we can disagree ;)

I've applied your suggestion, thanks!

No problems!

@jakebailey
Copy link
Member

@typescript-bot test this
@typescript-bot test top100
@typescript-bot user test this
@typescript-bot run dt
@typescript-bot perf test this faster
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the extended test suite on this PR at 27c593f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the diff-based user code test suite on this PR at 27c593f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the abridged perf test suite on this PR at 27c593f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the parallelized Definitely Typed test suite on this PR at 27c593f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the diff-based top-repos suite on this PR at 27c593f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Heya @jakebailey, I've started to run the tarball bundle task on this PR at 27c593f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 27, 2023

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/155712/artifacts?artifactName=tgz&fileId=F4D9F7172EFB05DD5D4254010EDE66EE19670D17486FFE6ACAF748CE0E57803802&fileName=/typescript-5.2.0-insiders.20230627.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user test suite comparing main and refs/pull/52112/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Unknown failure"
  • 1 instance of "Package install failed"

Otherwise...

Something interesting changed - please have a look.

Details

rxjs-src

/mnt/ts_downloads/rxjs-src/build.sh

  • [NEW] error TS2428: All declarations of 'WeakMap' must have identical type parameters.
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-52112/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
  • [MISSING] error TS2428: All declarations of 'WeakMap' must have identical type parameters.
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.symbol.wellknown.d.ts(140,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.collection.d.ts(63,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.iterable.d.ts(162,11)
    • /home/vsts/work/1/s/typescript-main/lib/lib.es2015.symbol.wellknown.d.ts(140,11)

@typescript-bot
Copy link
Collaborator

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

Here they are:

Comparison Report - main..52112

Metric main 52112 Delta Best Worst p-value
Angular - node (v16.17.1, x64)
Memory used 366,133k (± 0.01%) 366,114k (± 0.01%) ~ 366,055k 366,162k p=0.378 n=6
Parse Time 3.59s (± 0.33%) 3.59s (± 0.54%) ~ 3.57s 3.62s p=0.625 n=6
Bind Time 1.18s (± 0.44%) 1.18s (± 0.64%) ~ 1.17s 1.19s p=0.241 n=6
Check Time 9.65s (± 0.39%) 9.66s (± 0.52%) ~ 9.59s 9.74s p=0.807 n=6
Emit Time 7.96s (± 0.26%) 8.02s (± 1.28%) ~ 7.92s 8.16s p=0.514 n=6
Total Time 22.38s (± 0.26%) 22.45s (± 0.72%) ~ 22.27s 22.71s p=0.746 n=6
Compiler-Unions - node (v16.17.1, x64)
Memory used 192,843k (± 0.02%) 192,765k (± 0.04%) ~ 192,675k 192,859k p=0.093 n=6
Parse Time 1.58s (± 0.77%) 1.58s (± 1.24%) ~ 1.55s 1.60s p=0.933 n=6
Bind Time 0.82s (± 0.63%) 0.82s (± 0.99%) ~ 0.81s 0.83s p=0.140 n=6
Check Time 10.13s (± 0.68%) 10.13s (± 0.48%) ~ 10.07s 10.18s p=0.936 n=6
Emit Time 3.03s (± 0.55%) 3.03s (± 1.67%) ~ 2.97s 3.11s p=0.570 n=6
Total Time 15.56s (± 0.42%) 15.56s (± 0.47%) ~ 15.45s 15.66s p=0.936 n=6
Monaco - node (v16.17.1, x64)
Memory used 346,063k (± 0.01%) 346,054k (± 0.00%) ~ 346,036k 346,068k p=0.521 n=6
Parse Time 2.72s (± 0.28%) 2.73s (± 0.28%) ~ 2.72s 2.74s p=0.062 n=6
Bind Time 1.09s (± 0.75%) 1.09s (± 0.69%) ~ 1.08s 1.10s p=0.729 n=6
Check Time 7.87s (± 0.26%) 7.89s (± 0.60%) ~ 7.85s 7.98s p=0.513 n=6
Emit Time 4.47s (± 0.48%) 4.46s (± 0.76%) ~ 4.42s 4.51s p=0.628 n=6
Total Time 16.14s (± 0.17%) 16.17s (± 0.30%) ~ 16.12s 16.24s p=0.191 n=6
TFS - node (v16.17.1, x64)
Memory used 300,214k (± 0.00%) 300,226k (± 0.01%) ~ 300,200k 300,251k p=0.336 n=6
Parse Time 2.17s (± 0.39%) 2.18s (± 0.96%) ~ 2.15s 2.21s p=0.170 n=6
Bind Time 1.24s (± 1.41%) 1.25s (± 0.94%) ~ 1.23s 1.26s p=0.566 n=6
Check Time 7.31s (± 0.47%) 7.31s (± 0.29%) ~ 7.29s 7.34s p=0.872 n=6
Emit Time 4.32s (± 0.43%) 4.35s (± 0.54%) +0.03s (+ 0.77%) 4.32s 4.38s p=0.029 n=6
Total Time 15.03s (± 0.18%) 15.09s (± 0.37%) ~ 15.03s 15.17s p=0.126 n=6
material-ui - node (v16.17.1, x64)
Memory used 481,682k (± 0.01%) 481,699k (± 0.01%) ~ 481,651k 481,747k p=0.378 n=6
Parse Time 3.26s (± 0.72%) 3.26s (± 0.23%) ~ 3.25s 3.27s p=0.464 n=6
Bind Time 0.95s (± 0.79%) 0.96s (± 0.54%) ~ 0.95s 0.96s p=0.247 n=6
Check Time 18.25s (± 0.35%) 18.30s (± 0.23%) ~ 18.23s 18.35s p=0.228 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 22.46s (± 0.33%) 22.51s (± 0.21%) ~ 22.44s 22.57s p=0.261 n=6
xstate - node (v16.17.1, x64)
Memory used 561,157k (± 0.02%) 561,108k (± 0.01%) ~ 561,033k 561,224k p=0.423 n=6
Parse Time 4.00s (± 0.32%) 4.01s (± 0.66%) ~ 3.99s 4.06s p=0.565 n=6
Bind Time 1.73s (± 0.57%) 1.73s (± 0.85%) ~ 1.71s 1.75s p=0.933 n=6
Check Time 3.06s (± 0.38%) 3.05s (± 0.52%) ~ 3.03s 3.08s p=0.134 n=6
Emit Time 0.09s (± 0.00%) 0.09s (± 5.53%) ~ 0.09s 0.10s p=0.174 n=6
Total Time 8.88s (± 0.14%) 8.88s (± 0.26%) ~ 8.84s 8.91s p=0.739 n=6
System
Machine Namets-ci-ubuntu
Platformlinux 5.4.0-148-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
  • Angular - node (v16.17.1, x64)
  • Compiler-Unions - node (v16.17.1, x64)
  • Monaco - node (v16.17.1, x64)
  • TFS - node (v16.17.1, x64)
  • material-ui - node (v16.17.1, x64)
  • xstate - node (v16.17.1, x64)
Benchmark Name Iterations
Current 52112 6
Baseline main 6

Developer Information:

Download Benchmark

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top-repos suite comparing main and refs/pull/52112/merge:

Everything looks good!

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, it looks like the DT test run failed. Please check the log for more details.
You can check the log here.

@jakebailey
Copy link
Member

@typescript-bot run dt

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 28, 2023

Heya @jakebailey, I've started to run the parallelized Definitely Typed test suite on this PR at 27c593f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.
Everything looks the same!
You can check the log here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Status: Waiting on reviewers
Development

Successfully merging this pull request may close these issues.

Uncapitalize seems to not working within a type helper
5 participants