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

Do not widen literal types when emitting declarations #55445

Conversation

Andarist
Copy link
Contributor

fixes #55439

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Aug 21, 2023
@@ -82,7 +82,7 @@ declare function fs(x: string): void;
declare function fx(f: (x: "def") => void): void;
declare const x1: (x: string) => void;
declare const x2 = "abc";
declare const x3: string;
declare const x3: "def" | "abc";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand that there is this hidden widening thing encoded in the type and that it actually might widen when assigned to a mutable declaration. I think though that most people are not aware of this and it's more surprising that a type widened in the emitted declaration - people never audit them until something goes wrong.

The current behavior is also surprising in some cases. Let's expand on this very case here:

declare function f3<T>(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T;
declare function fo(x: Object): void;
declare function fx(f: (x: "def") => void): void;

const x3 = f3("abc", fo, fx);  // "abc" | "def"

let other: typeof x3 = x3
other = 'other' // error

As we can see we can't assign 'other' to other but yet in the emitted declaration file we can see this:

declare const x3: string;
declare let other: typeof x3;

It means that in practice we can end up with an error locally that wouldn't get reported if the same code would get used through the declaration file. I find that problematic.

The same could be said about the widening thing because "locally" an assignment would widen but if we change the declaration emit then the equivalent assignment would not widen since the widening behavior can't be encoded in the declaration file. I think though that's a smaller problem and a better tradeoff.

Copy link
Member

Choose a reason for hiding this comment

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

and it's more surprising that a type widened in the emitted declaration - people never audit them until something goes wrong.

Correct! And in most cases in declaration emit, the way we handle this is by preserving the literal initializer, rather than serializing a type annotation - that way the declaration has the same widening behavior as the original source. Eg, if you write

export const six = 6;

that is also, verbatim, the declaration emit. Unfortunately, right now this only covers simple cases of widening types - a heritage from our older, much simpler string-based declaration emitter.

IMO, rather than making an equally incorrect declaration in the other direction (preserving the literal types, rather than the widened type), we need to, once again, preserve the initializer to preserve the literal-widening behavior. In this case, that should mean making initializers with the same behavior.

Take the original issue:

function foo(): {y: 1} {
    return { y: 1 }
}

export const { y = 0 } = foo();

If we serialize it to

declare const _expr: {y: 1};
export declare const { y = 0 } = _expr;

y now behaves identically in the declaration file as it does in the original source file. Do note, this means that we'll need to allow identifiers in initializers in declaration files - something currently an error - but I think that's fine, especially since, despite the error, old versions of TS will still typecheck it correctly.

@Andarist Andarist force-pushed the fix/do-not-widen-literals-in-dts-emit branch from a03e6aa to 718120d Compare August 21, 2023 09:10
Comment on lines +77 to +78
declare const c13: "abc" | "def";
declare const c14: 123 | 456;
Copy link
Member

Choose a reason for hiding this comment

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

Interestingly I think this case came up during our talks about isolatedDeclarations. The code for these two is:

const c13 = Math.random() > 0.5 ? "abc" : "def";
const c14 = Math.random() > 0.5 ? 123 : 456;

@RyanCavanaugh @DanielRosenwasser

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I just realized who reported the bug, it is directly related!

@typescript-bot typescript-bot added For Backlog Bug PRs that fix a backlog bug and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Sep 7, 2023
@jakebailey
Copy link
Member

I don't know if this will actually net any test changes but:

@typescript-bot test top200
@typescript-bot user test this
@typescript-bot run dt
@typescript-bot perf test this
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 2023

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 7, 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/157484/artifacts?artifactName=tgz&fileId=05A23ABD39BB154CDF43EB858BACAD333456F2CA4ECF5EAD13702CAB49048C6202&fileName=/typescript-5.3.0-insiders.20230907.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/pr-build@5.3.0-pr-55445-6".;

@typescript-bot
Copy link
Collaborator

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

There were infrastructure failures potentially unrelated to your change:

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

Otherwise...

Everything looks good!

@typescript-bot
Copy link
Collaborator

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

Here they are:

Compiler

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Angular - node (v16.17.1, x64)
Memory used 300,276k (± 0.01%) 300,259k (± 0.00%) ~ 300,242k 300,278k p=0.128 n=6
Parse Time 3.00s (± 0.25%) 3.01s (± 0.17%) ~ 3.00s 3.01s p=0.247 n=6
Bind Time 0.93s (± 0.00%) 0.93s (± 0.44%) ~ 0.93s 0.94s p=0.405 n=6
Check Time 9.31s (± 0.34%) 9.31s (± 0.21%) ~ 9.29s 9.34s p=0.871 n=6
Emit Time 7.64s (± 0.27%) 7.61s (± 0.19%) -0.03s (- 0.35%) 7.59s 7.63s p=0.035 n=6
Total Time 20.88s (± 0.20%) 20.86s (± 0.09%) ~ 20.84s 20.89s p=0.294 n=6
Compiler-Unions - node (v16.17.1, x64)
Memory used 193,955k (± 0.02%) 193,887k (± 0.03%) ~ 193,828k 193,974k p=0.066 n=6
Parse Time 1.58s (± 0.52%) 1.58s (± 0.00%) ~ 1.58s 1.58s p=0.405 n=6
Bind Time 0.80s (± 0.65%) 0.80s (± 0.51%) ~ 0.79s 0.80s p=0.595 n=6
Check Time 9.92s (± 0.33%) 9.92s (± 0.54%) ~ 9.84s 10.00s p=1.000 n=6
Emit Time 2.74s (± 0.19%) 2.74s (± 0.38%) ~ 2.72s 2.75s p=0.242 n=6
Total Time 15.04s (± 0.19%) 15.03s (± 0.35%) ~ 14.97s 15.12s p=0.807 n=6
Monaco - node (v16.17.1, x64)
Memory used 347,174k (± 0.01%) 347,185k (± 0.01%) ~ 347,132k 347,228k p=0.689 n=6
Parse Time 2.69s (± 0.20%) 2.69s (± 0.28%) ~ 2.68s 2.70s p=0.137 n=6
Bind Time 0.99s (± 0.00%) 0.99s (± 0.00%) ~ 0.99s 0.99s p=1.000 n=6
Check Time 7.94s (± 0.25%) 7.93s (± 0.50%) ~ 7.87s 7.98s p=0.572 n=6
Emit Time 4.27s (± 0.39%) 4.26s (± 0.33%) ~ 4.24s 4.28s p=0.328 n=6
Total Time 15.88s (± 0.20%) 15.87s (± 0.36%) ~ 15.78s 15.94s p=0.627 n=6
TFS - node (v16.17.1, x64)
Memory used 301,171k (± 0.00%) 301,177k (± 0.01%) ~ 301,157k 301,206k p=0.873 n=6
Parse Time 2.17s (± 0.47%) 2.18s (± 0.68%) ~ 2.15s 2.19s p=0.324 n=6
Bind Time 1.11s (± 0.46%) 1.11s (± 0.37%) ~ 1.11s 1.12s p=0.595 n=6
Check Time 7.23s (± 0.26%) 7.24s (± 0.30%) ~ 7.21s 7.27s p=0.462 n=6
Emit Time 3.98s (± 0.20%) 3.98s (± 0.25%) ~ 3.97s 3.99s p=0.862 n=6
Total Time 14.50s (± 0.21%) 14.51s (± 0.25%) ~ 14.45s 14.56s p=0.629 n=6
material-ui - node (v16.17.1, x64)
Memory used 479,466k (± 0.00%) 479,470k (± 0.00%) ~ 479,461k 479,476k p=0.470 n=6
Parse Time 3.15s (± 0.16%) 3.15s (± 0.16%) ~ 3.14s 3.15s p=1.000 n=6
Bind Time 0.91s (± 0.00%) 0.91s (± 0.00%) ~ 0.91s 0.91s p=1.000 n=6
Check Time 17.81s (± 0.38%) 17.80s (± 0.41%) ~ 17.72s 17.89s p=0.809 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 21.86s (± 0.31%) 21.86s (± 0.34%) ~ 21.78s 21.95s p=0.872 n=6
xstate - node (v16.17.1, x64)
Memory used 542,853k (± 0.01%) 542,851k (± 0.02%) ~ 542,741k 543,063k p=0.423 n=6
Parse Time 3.68s (± 0.27%) 3.69s (± 0.17%) ~ 3.68s 3.70s p=0.151 n=6
Bind Time 1.46s (± 5.19%) 1.40s (± 4.57%) ~ 1.34s 1.46s p=0.091 n=6
Check Time 3.20s (± 2.22%) 3.23s (± 2.88%) ~ 3.12s 3.33s p=0.872 n=6
Emit Time 0.09s (± 5.95%) 0.08s (± 6.19%) ~ 0.08s 0.09s p=0.311 n=6
Total Time 8.42s (± 0.52%) 8.40s (± 0.48%) ~ 8.34s 8.46s p=0.808 n=6
System info unknown
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 pr 6
Baseline baseline 6

tsserver

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 2,487ms (± 0.27%) 2,490ms (± 0.22%) ~ 2,483ms 2,498ms p=0.377 n=6
Req 2 - geterr 5,939ms (± 0.49%) 5,928ms (± 0.34%) ~ 5,906ms 5,959ms p=0.748 n=6
Req 3 - references 345ms (± 0.48%) 342ms (± 0.22%) -2ms (- 0.68%) 341ms 343ms p=0.011 n=6
Req 4 - navto 276ms (± 0.70%) 276ms (± 0.93%) ~ 274ms 281ms p=0.325 n=6
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) ~ 1,356 1,356 p=1.000 n=6
Req 5 - completionInfo 78ms (± 3.67%) 84ms (± 8.89%) ~ 76ms 93ms p=0.123 n=6
CompilerTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 2,620ms (± 0.71%) 2,620ms (± 0.63%) ~ 2,591ms 2,638ms p=0.810 n=6
Req 2 - geterr 4,778ms (± 0.20%) 4,778ms (± 0.10%) ~ 4,774ms 4,785ms p=0.575 n=6
Req 3 - references 350ms (± 0.29%) 351ms (± 0.31%) ~ 350ms 353ms p=0.310 n=6
Req 4 - navto 269ms (± 0.38%) 270ms (± 0.20%) ~ 269ms 270ms p=0.663 n=6
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) ~ 1,518 1,518 p=1.000 n=6
Req 5 - completionInfo 79ms (± 0.65%) 79ms (± 0.52%) ~ 79ms 80ms p=0.595 n=6
xstateTSServer - node (v16.17.1, x64)
Req 1 - updateOpen 2,707ms (± 0.16%) 2,709ms (± 0.20%) ~ 2,702ms 2,716ms p=0.629 n=6
Req 2 - geterr 1,938ms (± 2.60%) 1,953ms (± 2.18%) ~ 1,872ms 1,985ms p=0.574 n=6
Req 3 - references 139ms (± 3.15%) 135ms (± 7.79%) ~ 114ms 141ms p=0.511 n=6
Req 4 - navto 359ms (± 0.94%) 361ms (± 1.14%) ~ 354ms 364ms p=0.465 n=6
Req 5 - completionInfo count 2,071 (± 0.00%) 2,071 (± 0.00%) ~ 2,071 2,071 p=1.000 n=6
Req 5 - completionInfo 320ms (± 2.05%) 321ms (± 1.86%) ~ 313ms 325ms p=0.934 n=6
System info unknown
Hosts
  • node (v16.17.1, x64)
Scenarios
  • CompilerTSServer - node (v16.17.1, x64)
  • Compiler-UnionsTSServer - node (v16.17.1, x64)
  • xstateTSServer - node (v16.17.1, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
tsc-startup - node (v16.17.1, x64)
Execution time 156.31ms (± 0.14%) 156.48ms (± 0.19%) +0.18ms (+ 0.11%) 154.80ms 161.58ms p=0.000 n=600
tsserver-startup - node (v16.17.1, x64)
Execution time 230.59ms (± 0.15%) 231.21ms (± 0.14%) +0.62ms (+ 0.27%) 229.87ms 235.56ms p=0.000 n=600
tsserverlibrary-startup - node (v16.17.1, x64)
Execution time 235.55ms (± 0.22%) 236.47ms (± 0.11%) +0.93ms (+ 0.39%) 235.11ms 241.27ms p=0.000 n=600
typescript-startup - node (v16.17.1, x64)
Execution time 236.35ms (± 0.32%) 236.18ms (± 0.14%) -0.17ms (- 0.07%) 234.70ms 240.19ms p=0.006 n=600
System info unknown
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 pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@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 Sep 8, 2023

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

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

Everything looks good!

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

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

This seems okay and what we are trying to go for to make isolatedDeclarations easier, correct?

@@ -82,7 +82,7 @@ declare function fs(x: string): void;
declare function fx(f: (x: "def") => void): void;
declare const x1: (x: string) => void;
declare const x2 = "abc";
declare const x3: string;
declare const x3: "def" | "abc";
Copy link
Member

Choose a reason for hiding this comment

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

and it's more surprising that a type widened in the emitted declaration - people never audit them until something goes wrong.

Correct! And in most cases in declaration emit, the way we handle this is by preserving the literal initializer, rather than serializing a type annotation - that way the declaration has the same widening behavior as the original source. Eg, if you write

export const six = 6;

that is also, verbatim, the declaration emit. Unfortunately, right now this only covers simple cases of widening types - a heritage from our older, much simpler string-based declaration emitter.

IMO, rather than making an equally incorrect declaration in the other direction (preserving the literal types, rather than the widened type), we need to, once again, preserve the initializer to preserve the literal-widening behavior. In this case, that should mean making initializers with the same behavior.

Take the original issue:

function foo(): {y: 1} {
    return { y: 1 }
}

export const { y = 0 } = foo();

If we serialize it to

declare const _expr: {y: 1};
export declare const { y = 0 } = _expr;

y now behaves identically in the declaration file as it does in the original source file. Do note, this means that we'll need to allow identifiers in initializers in declaration files - something currently an error - but I think that's fine, especially since, despite the error, old versions of TS will still typecheck it correctly.

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

While talking to @weswigham, I thought of this example:

// @filename: a.ts
export const value = Math.random() > 0.5 ? "foo" : "bar";

// @filename: b.ts
import * as a from "./a";
export const x1 = a.value;
export let x2 = a.value;

When you run tsc from this PR, you get:

// @filename: a.d.ts
export declare const value: "foo" | "bar";

// @filename: b.d.ts
export declare const x1: "foo" | "bar";
export declare let x2: string;

Now, remove a.ts to simulate using it externally (or probably even in build mode), then rerun tsc, and you get:

// @filename: b.d.ts
export declare const x1: "foo" | "bar";
export declare let x2: "foo" | "bar";

That seems pretty bad. Not sure how to proceed at this point.

Though, weren't we banning ternaries in isolatedDeclarations anyway?

@Andarist
Copy link
Contributor Author

I could implement @weswigham's suggestion but - like @jakebailey mentioned - it's unclear what to do about ternaries. I'd prefer to work on the further improvements to this PR in one go so I'm waiting for further feedback/decision about this.

Though, weren't we banning ternaries in isolatedDeclarations anyway?

I think that the behavior in question is confusing even outside of this mode. Perhaps it should be considered first without taking this mode into account. I imagine that a solution for this issue would automatically make isolatedDeclarations able to reflect the changes though.

@jakebailey
Copy link
Member

FWIW I think this PR can be closed, just because I don't think we're going to be able to fix this by just not widening; we need some other mechanism to do this.

@Andarist Andarist closed this Mar 26, 2024
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
None yet
Development

Successfully merging this pull request may close these issues.

type gets widened on declaration emit when inferred in destructuring context
5 participants