Skip to content

Support contextual type discrimination based on template literal expressions #62203

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 6 commits into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

@Andarist Andarist commented Aug 5, 2025

Template literal types are allowed as discriminant properties since #46137
but template literal expressions can't be used to discriminate contextual types which manifests in issues like:

type Target =
  | { type: `${string}_FOO`; cb: (arg: number) => void }
  | { type: `${string}_BAR`; cb: (arg: string) => void };

declare const str: string;

const obj1: Target = {
  type: `${str}_FOO`,
  cb: (arg) => {}, // implicit any
};

This PR closes the gap by preventing potential template literal types discriminants from widening to string in the context of context-free types. I don't think this changes all that much the meaning of a context-free type is. Let's consider:

type Target2 =
  | { type: "_FOO"; cb: (arg: number) => void }
  | { type: "_BAR"; cb: (arg: string) => void };

const obj2: Target2 = {
  type: "_FOO",
  cb: (arg) => {},
};

The context-free type of that "_FOO" expression is a fresh "_FOO" string literal type. In the same fashion, a context-free type of a template literal expression can be that unwidened template literal type.

fixes #57231

@Copilot Copilot AI review requested due to automatic review settings August 5, 2025 08:11
@github-project-automation github-project-automation bot moved this to Not started in PR Backlog Aug 5, 2025
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for contextual type discrimination based on template literal types. It allows template literal expressions to be used to discriminate contextual types, fixing cases where template literal expressions couldn't properly narrow union types during object literal type checking.

Key changes:

  • Introduces a new contextFreeType intrinsic type to handle context-free template literal type inference
  • Modifies template literal type checking to prevent widening to string when template literals can serve as discriminants
  • Updates getContextFreeType to use the new context-free type instead of anyType

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/compiler/checker.ts Adds contextFreeType intrinsic and updates template literal type checking logic to preserve discriminant information
tests/cases/conformance/types/union/discriminatedUnionTypesOverlappingDiscriminants1.ts New comprehensive test cases for template literal discriminants in various scenarios
tests/cases/compiler/templateLiteralConstantEvaluation.ts Adds test case for empty string template literal evaluation
tests/cases/compiler/evolvingArrayTemplateLiterals1.ts New test for template literal behavior in evolving arrays
tests/cases/compiler/discriminantUsingEvaluatableTemplateExpression.ts Adds test for evaluatable template expression discrimination
Various baseline files Expected type checking results and error outputs for the new test cases
Comments suppressed due to low confidence (2)

@Andarist Andarist changed the title Support contextual type discrimination based on template literal types Support contextual type discrimination based on template literal expressions Aug 5, 2025
@jakebailey
Copy link
Member

@typescript-bot test it

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 5, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started ✅ Results
user test this ✅ Started ✅ Results
run dt ✅ Started ✅ Results
perf test this faster ✅ Started 👀 Results

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

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user tests with tsc comparing main and refs/pull/62203/merge:

Everything looks good!

@typescript-bot
Copy link
Collaborator

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

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 34 34 ~ ~ ~ p=1.000 n=6
Symbols 62,370 62,370 ~ ~ ~ p=1.000 n=6
Types 50,386 50,387 +1 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 192,929k (± 0.01%) 194,229k (± 0.94%) +1,299k (+ 0.67%) 192,973k 196,591k p=0.005 n=6
Parse Time 1.30s (± 0.84%) 1.31s ~ ~ ~ p=0.071 n=6
Bind Time 0.73s 0.73s ~ ~ ~ p=1.000 n=6
Check Time 9.74s (± 0.37%) 9.77s (± 0.29%) ~ 9.72s 9.79s p=0.195 n=6
Emit Time 2.74s (± 0.38%) 2.73s (± 0.73%) ~ 2.71s 2.76s p=0.222 n=6
Total Time 14.51s (± 0.24%) 14.53s (± 0.23%) ~ 14.49s 14.58s p=0.422 n=6
angular-1 - node (v18.15.0, x64)
Errors 56 56 ~ ~ ~ p=1.000 n=6
Symbols 948,914 948,914 ~ ~ ~ p=1.000 n=6
Types 410,884 410,886 +2 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 1,226,430k (± 0.01%) 1,226,446k (± 0.01%) ~ 1,226,347k 1,226,546k p=0.689 n=6
Parse Time 6.49s (± 0.53%) 6.54s (± 0.71%) ~ 6.49s 6.62s p=0.064 n=6
Bind Time 1.88s (± 0.29%) 1.88s (± 0.55%) ~ 1.86s 1.89s p=0.663 n=6
Check Time 31.98s (± 0.38%) 32.01s (± 0.35%) ~ 31.79s 32.11s p=0.334 n=6
Emit Time 14.73s (± 0.59%) 14.78s (± 0.41%) ~ 14.72s 14.89s p=0.747 n=6
Total Time 55.08s (± 0.35%) 55.20s (± 0.25%) ~ 54.94s 55.31s p=0.229 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,548,614 2,548,614 ~ ~ ~ p=1.000 n=6
Types 903,207 903,230 +23 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 2,833,394k (± 0.01%) 2,833,374k (± 0.00%) ~ 2,833,272k 2,833,636k p=0.575 n=6
Parse Time 8.78s (± 0.27%) 8.77s (± 0.41%) ~ 8.72s 8.83s p=0.627 n=6
Bind Time 2.25s (± 0.81%) 2.25s (± 0.36%) ~ 2.24s 2.26s p=0.801 n=6
Check Time 86.00s (± 0.49%) 85.63s (± 0.66%) ~ 84.96s 86.56s p=0.173 n=6
Emit Time 2.02s (±41.97%) 2.02s (±42.00%) ~ 0.30s 2.49s p=1.000 n=6
Total Time 99.05s (± 0.92%) 98.67s (± 0.50%) ~ 97.89s 99.23s p=0.170 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,227,066 1,227,071 +5 (+ 0.00%) ~ ~ p=0.001 n=6
Types 267,480 267,496 +16 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 2,363,395k (± 0.02%) 2,607,004k (±14.46%) ~ 2,363,251k 3,094,497k p=0.230 n=6
Parse Time 5.20s (± 0.37%) 5.27s (± 1.15%) ~ 5.19s 5.34s p=0.077 n=6
Bind Time 1.78s (± 1.21%) 1.77s (± 1.36%) ~ 1.74s 1.81s p=0.511 n=6
Check Time 35.36s (± 0.19%) 35.44s (± 0.52%) ~ 35.25s 35.73s p=0.575 n=6
Emit Time 2.97s (± 1.81%) 2.99s (± 0.75%) ~ 2.97s 3.03s p=0.575 n=6
Total Time 45.32s (± 0.26%) 45.47s (± 0.49%) ~ 45.27s 45.82s p=0.378 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,227,066 1,227,071 +5 (+ 0.00%) ~ ~ p=0.001 n=6
Types 267,480 267,496 +16 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 2,797,762k (±14.24%) 3,040,566k (± 9.77%) ~ 2,433,507k 3,162,728k p=0.230 n=6
Parse Time 6.85s (± 1.87%) 6.89s (± 0.83%) ~ 6.78s 6.93s p=1.000 n=6
Bind Time 2.16s (± 1.21%) 2.19s (± 1.34%) ~ 2.15s 2.22s p=0.127 n=6
Check Time 42.81s (± 0.49%) 43.02s (± 0.45%) ~ 42.68s 43.19s p=0.128 n=6
Emit Time 3.55s (± 1.99%) 3.53s (± 1.62%) ~ 3.46s 3.61s p=0.630 n=6
Total Time 55.38s (± 0.75%) 55.62s (± 0.44%) ~ 55.17s 55.88s p=0.298 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 262,555 262,560 +5 (+ 0.00%) ~ ~ p=0.001 n=6
Types 107,165 107,169 +4 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 441,955k (± 0.01%) 441,988k (± 0.01%) ~ 441,927k 442,053k p=0.078 n=6
Parse Time 4.36s (± 0.55%) 4.39s (± 1.07%) ~ 4.35s 4.48s p=0.253 n=6
Bind Time 1.63s (± 1.05%) 1.62s (± 0.72%) ~ 1.61s 1.64s p=0.323 n=6
Check Time 23.41s (± 0.40%) 23.46s (± 0.25%) ~ 23.38s 23.55s p=0.198 n=6
Emit Time 1.90s (± 0.86%) 1.91s (± 0.98%) ~ 1.88s 1.93s p=0.373 n=6
Total Time 31.31s (± 0.27%) 31.38s (± 0.19%) ~ 31.29s 31.43s p=0.170 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 71 71 ~ ~ ~ p=1.000 n=6
Symbols 225,367 225,367 ~ ~ ~ p=1.000 n=6
Types 94,290 94,291 +1 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 371,089k (± 0.01%) 371,123k (± 0.02%) ~ 371,060k 371,245k p=0.471 n=6
Parse Time 2.86s (± 1.41%) 2.89s (± 0.80%) ~ 2.86s 2.92s p=0.295 n=6
Bind Time 1.60s (± 1.10%) 1.60s (± 0.79%) ~ 1.58s 1.61s p=0.805 n=6
Check Time 16.40s (± 0.55%) 16.46s (± 0.27%) ~ 16.41s 16.53s p=0.196 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 20.87s (± 0.55%) 20.95s (± 0.31%) ~ 20.86s 21.05s p=0.199 n=6
vscode - node (v18.15.0, x64)
Errors 1 1 ~ ~ ~ p=1.000 n=6
Symbols 3,562,109 3,562,109 ~ ~ ~ p=1.000 n=6
Types 1,201,176 1,201,211 +35 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 3,606,481k (± 0.01%) 3,606,803k (± 0.01%) ~ 3,606,521k 3,607,204k p=0.128 n=6
Parse Time 15.38s (± 3.71%) 15.15s (± 0.16%) ~ 15.12s 15.18s p=0.518 n=6
Bind Time 4.84s (± 1.26%) 4.86s (± 0.53%) ~ 4.83s 4.90s p=0.807 n=6
Check Time 99.51s (± 0.94%) 99.52s (± 1.33%) ~ 98.63s 102.11s p=0.936 n=6
Emit Time 31.50s (± 7.87%) 31.66s (± 9.12%) ~ 30.22s 37.55s p=1.000 n=6
Total Time 151.23s (± 2.12%) 151.19s (± 2.15%) ~ 149.03s 157.23s p=0.936 n=6
webpack - node (v18.15.0, x64)
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 321,124 321,124 ~ ~ ~ p=1.000 n=6
Types 140,106 140,124 +18 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 477,577k (± 0.01%) 477,556k (± 0.03%) ~ 477,326k 477,766k p=0.936 n=6
Parse Time 4.32s (± 0.77%) 4.34s (± 0.52%) ~ 4.30s 4.36s p=0.295 n=6
Bind Time 1.86s (± 0.56%) 1.84s (± 1.42%) ~ 1.82s 1.89s p=0.064 n=6
Check Time 21.15s (± 0.39%) 21.16s (± 0.37%) ~ 21.05s 21.26s p=1.000 n=6
Emit Time 0.00s (±244.70%) 0.00s ~ ~ ~ p=0.405 n=6
Total Time 27.34s (± 0.29%) 27.34s (± 0.34%) ~ 27.18s 27.45s p=0.936 n=6
xstate-main - node (v18.15.0, x64)
Errors 30 30 ~ ~ ~ p=1.000 n=6
Symbols 663,258 663,258 ~ ~ ~ p=1.000 n=6
Types 198,184 198,185 +1 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 570,255k (± 0.03%) 570,253k (± 0.03%) ~ 570,085k 570,433k p=0.575 n=6
Parse Time 4.27s (± 0.36%) 4.28s (± 0.61%) ~ 4.24s 4.31s p=0.255 n=6
Bind Time 1.33s (± 1.13%) 1.33s (± 0.61%) ~ 1.32s 1.34s p=0.498 n=6
Check Time 20.12s (± 1.71%) 20.26s (± 1.87%) ~ 19.89s 20.66s p=0.471 n=6
Emit Time 0.00s (±244.70%) 0.00s (±244.70%) ~ 0.00s 0.01s p=1.000 n=6
Total Time 25.71s (± 1.32%) 25.88s (± 1.49%) ~ 25.48s 26.31s p=0.378 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top 400 repos with tsc comparing main and refs/pull/62203/merge:

Everything looks good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Not started
Development

Successfully merging this pull request may close these issues.

discriminated union type matching behaviour changed starting from 5.2.x
3 participants