Design Meeting Notes, 12/10/2021 #47151
Copy link
Copy link
Closed
Labels
Design NotesNotes from our design meetingsNotes from our design meetings
Description
"Correlated Union Types"
type NumberRecord = { kind: "n", v: number, f: (v: number) => void };
type StringRecord = { kind: "s", v: string, f: (v: string) => void };
type BooleanRecord = { kind: "b", v: boolean, f: (v: boolean) => void };
type UnionRecord = NumberRecord | StringRecord | BooleanRecord;
function processRecord(record: UnionRecord) {
record.f(record.v); // error!
// error msg in TS3.2 and below: can't call union of functions
// error msg in TS3.3 and up: record.v not assignable to never
}-
We want to be able to feed the callback with the original value; but now
vis a union type andf's parameter type is an intersection; that's not precise forv, and that intersection now only acceptsnever. -
People have speculated that maybe we need existential types so that we can say "this function takes whatever type
vhas" and can feed it through. -
Can try to re-model this in the form of a re-indexed mapped type.
interface TypeMap { "n": number; "s": string; "b": boolean; } type UnionRecord<P extends keyof TypeMap> = { [K in P}]: { kind: K; v: TypeMap[K]; f: (p: TypeMap[K]) => void; } }[K]; function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) { record.f(record.v); }
-
With some improvements of inference and apparent types, we can enable most of these cases - 2 improvements on the scale of what feel like bug fixes.
- First is inference to the IIFE-like mapped object pattern from above.
- Second is how we present the apparent members of certain types when you index into an already-instantiated mapped object type (a "table type") with some generic key.
{ [P in K]: Foo<P> }[X] => Foo<X>- Does that mean that if you aren't using a type that was declared as a mapped type, then we won't get a more precise type?
- Yes, if you leave off types on these objects.
- I guess we don't have an easy way to correlate.
- We could try to do something there...but that's a bit of a stretch.
- Yes, if you leave off types on these objects.
-
This is a weird pattern. Would like to not have to teach people to write this.
Modules Migration Status Update
- A lot of time running tests.
- A lot of time is going into getters for every single binding.
- Had TypeScript generate to CJS, had esbuild run on top of that.
- Swapped TS to generate ESM, and esbuild generates the same sorts of bindings - so no optimization there.
- 25 minutes to run the full tests! Thanks to the multiple levels of indirections of calling functions.
- Could switch to ES3 emit to avoid accessors.
- Also could switch to native ESM emit.
- No telling how slow ESM live bindings would be.
- Could avoid indirect barrel imports.
- Looked at bundler front-ends that are largely esbuild wrappers.
- But these tend to push for the optimized build to use Rollup.
- [[Whole discussion on what the right shipping format and layout to send.]]
- Dogfooding with modules showed us just how slow this is.
- Even getting complaints on tslib: __createBinding performance issue on web browsers tslib#165
- Conclusion
- Investigate the
__createBindingsissue. - Investigate alternative bundlers.
- Investigate the
Ensuring Modules Conform to a Type
- Lots of people would like to be able to ensure "this file has this sort of shape."
- Then "I would like this folder to describe this shape".
export implements SomeTypethat- PR suggests everything should be contextually typed.
- You can emulate this today with a self-import.
- Without contextual typing, this is possibly just sugar that saves you 2 lines. Maybe not as compelling.
- This links to typing default exports - is that the real feature?
- Out of time - revisit in the new year.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Design NotesNotes from our design meetingsNotes from our design meetings
Activity
DanielRosenwasser commentedon Dec 14, 2021
PR up for correlated union types is up #47109
ShuiRuTian commentedon Dec 15, 2021
What is the difference between #46567 and #35561?
orta commentedon Dec 15, 2021
#46567 builds on #35561
evanw commentedon Dec 21, 2021
Hello! I'm the developer behind esbuild. I noticed that you're running the TypeScript compiler through esbuild, so I couldn't help trying it out myself. I've been meaning to do cross-module enum inlining so I did a quick implementation and tried it out on the branch in #46567. Here's a simple benchmark of the result:
lib/tsc.jsThese tests measure the time to type check the Rollup code base (with fixes for type errors due to the TypeScript upgrade). As you can see, bundling with esbuild after inlining enums results in a TypeScript compiler build that's the same speed as the one in
lib/tsc.js.But I had to fix a bug in that branch that was slowing things down a lot (the "fixes" columns in the table):
This code was picking up on the local versions of
MapandSetinstead of the global versions, so it was always installing the shim version and never using the native version. Using the nativeMapandSetis a 50% speedup. I assume you'll figure this out too if you haven't already, but I thought I'd save you the trouble in case it's helpful.Anyway just wanted to share this result and bug fix. It's totally up to you whether or not you use esbuild, so no pressure. I imagine there are concerns about wanting to dogfood the TypeScript compiler. I was actually surprised to see you trying out esbuild at all.
Edit: Oh yeah, also I shipped cross-module enum inlining in esbuild version 0.14.7 if you want to try it out.
DanielRosenwasser commentedon Dec 21, 2021
Evan Wallace (@evanw) you're a life-saver.
Eli Barzilay (@elibarzilay) can you experiment with the above? The only catch is that we don't always have
globalThisdefined in older JS versions, so I think you'll have to try something likeDanielRosenwasser commentedon Dec 21, 2021
I think one other thing to consider is that this is something the conversion script kept working, but silently wrong. Seems like nested namespaces and places where we use local
declares might be something to watch out for.elibarzilay commentedon Jan 12, 2022
Evan Wallace (@evanw) Thanks for that and sorry that I got to it so late. I'm guessing that you were running esbuild directly on the TS sources--? I'm using it with the CJS output of the compiler, and that has some differences in runtime -- specifically, my concern was the time it takes to run the full set of TS tests... First, I didn't see any change when bumping the version (which is probably expected, given that it's the cjs output).
Second, the Map/Set bug didn't actually affect the cjs output since it doesn't create a global but uses
exports.Mapdirectly -- I did try at some point to run esbuild directly on the ts sources which was very slow, so I'm wondering if that could have caused some of the cost (especially if comes on top of the getter cost). In any case, it is definitely a bug, and I just opened #47409 for that.evanw commentedon Jan 13, 2022
Yes, that's correct. I did that because you need to run esbuild on the TS sources to take advantage of TS features such as enum inlining, so that's the fastest way to run the code.
mohsen1 commentedon Jan 18, 2022
RE: #38511
Can you explain this with an example please? I don't fully understand how importing self can achieve the same thing as proposed.
Apparently
export default: Type <thing>syntax was introducing so much syntax ambiguity that this request has been stalled (#3792). Withexport implementswe can type the default export without limitations mentioned there:DanielRosenwasser commentedon Jan 19, 2022
In theory you can write