in ,

Google feedback on TypeScript 3.5 · Issue # 33272 · microsoft / TypeScript, Hacker News

Google feedback on TypeScript 3.5 · Issue # 33272 · microsoft / TypeScript, Hacker News


          

We recently upgraded Google to use TypeScript 3.5. Here is some feedback on the upgrade.

(For background, recall that Google is amonorepo of billions of lines of code. We use a single version of TypeScript and a single set of compiler flags across all teams and upgrade these simultaneously for everyone.)

We know and expect every TypeScript upgrade to involve some work. For example, improvements to the standard library are expected and welcomed by us, even though they may mean removing similar but incompatible definitions from our own code base. However, TypeScript 3.5 was a lot more work for us than other recent TypeScript upgrades.

There were three main changes in 3.5 that made it especially painful. (The other changes were also required work, but these three are worth some extra discussion.) We believe most of these changes were intentional and intended to improve type checking, but we also believe the TypeScript team understands that type checking is always just a tradeoff between safety and ergonomics.

It is our hope that this report about TS 3.5 as applied to a large codebase will help the TypeScript team better evaluate future situations that are similar, and we make some recommendations.

Implicit default for generics

This was the headlinebreaking changein 3.5. We agree with the end goal of this change, and understand that it will shake up user code.

Historically when TypeScript has introduced type system changes like this, they were behind a flag.

Suggestion:Using a flag here would have allowed us to adapt to this change separately from the other breaking changes in 3.5.

The main way this failed is in circumstances where code had a generic that was irrelevant to what the code did. For example, consider some code that has a Promise resolve, but doesn’t care about what value the Promise to resolves to:

function dontCarePromise () {   return new Promise ((resolve)=>{     resolve ();   }); }

Because the generic is unbound, under 3.4 this wasPromiseand under 3.5 this becomesPromise. If a user of this function wrote down the type of that promise anywhere, e.g .:

const myPromise: Promise=dontCarePromise ();

it now became a type error for no user benefit.

The bulk of churn from this generics change was in code like this, where someone wrote a{}mostly because it was what the compiler said without really caring what type it was.

One common concrete example of this don’t-care pattern are the typings for thed3library, which has a very complexd3.Selectionthat takes four generic arguments. In the vast majority of use cases the last two are irrelevant, but any time someone saves aSelectioninto a member variable, they ended up writing down whatever type TS inferred at that time, eg:

mySel: d3.Selection;

The 3.5 generics change means that{}becameunknownsimultaneously in almost every interaction with d3.

Suggestion: Our main conclusion about specifically d3 is that the d3 typings are not great and need some attention. There are some other type-level issues with them (outside of this upgrade) that I’d like to go into more, but it’s not relevant to this upgrade.

Another troublesome pattern are what we call “return-only generics”, which is any pattern where a generic function only uses it in the return type. I think the TypeScript team already knows how problematic these are, with lots of inference surprises. For example, in the presence of a return only generic, the code:

expectsString (myFunction ());

can be legal while the innocent-looking refactor

const x=myFunction (); expectsString (x);

can then fail.

Suggestion: We’d be interested in seeing whether TypeScript could compile-fail on this pattern entirely, rather than picking a top type ({}orunknown). Users are happy to specify the generic type at the call site, e.g.myFunction()but right now the compiler doesn’t help them see when they need it. For example, maybe the declarationmyFunction(... )could always require a specificTto be inferred, because you can always writemyFunction()for the case where you are ok with a default.

One other common cause of return-only generics is a dependency injection pattern. Consider some test framework that provides some sort of injector function:

function getService(service: Ctor): T;

whereCtoris some type that matches class values. The intended use of this is e.g.

class MyService {…} const myService=getService (MyService);

This works great up untilMyServiceis generic, at which point this again picks an arbitraryfor the return type. The problem here is that we pass theMyServicevaluetogetService, but then we get back theMyServicetype, which needs a generic.

One last source of return-only generics that we discovered is that the generic doesn’t need to be in the return type. See the next section.

filter (Boolean)

TypeScript 3.5 changed the type of theBooleanfunction, which coerces a value toboolean, from (effectively)

function Boolean (value ?: any): boolean;

to

function Boolean(value ?: T): boolean;

These look like they might behave very similarly. But imagine a function that takes a predicate and returns an array filter, and using it with the above:

function filter(predicate: (t: T)=>boolean): (ts: T [])=>T []; const myFilter=filter (Boolean);

With the 3.4 definition of Boolean,Tis pinned toanyand (myFilter) becomes a function fromany []toany []. With the 3.5 definition,Tremains generic.

We believe this change was intentional, to improve scenarios like this.

The RxJS library uses a more complex variant of the above pattern, and a common use of it creates a function composition pipeline with a (filter) Boolean)much like the above. With TS 3.4, users were accidentally gettinganydownstream of that point. With TS 3.5, they instead get a genericTthat then feeds into a larger inference . You can readthe full RxJS bugfor some more context.

One of the big surprises here is that everyone using RxJS was getting an unexpectedanyat this point. We knew to look foranyinreturn types, but now we know that even if you accept ananyin anargument type, via inference this can cause other types to becomeany.

Suggestion:A more sophisticated definition ofBoolean, one that removed null | undefined from its generic, might have helped, but from our experiments in this area there are further surprises (search for “backwards” on the above RxJS bug). This was also not mentioned in the list of breaking changes in 3.5. It’s possible its impact was underestimated because it disproportionately affects RxJS (and Angular) users.

Set

In TypeScript 3.4,

const s=new Set ();

gave you back aSet. (It’s actually a kind of amusing type, because in some sense almost everything still works as expected – you can put stuff in the set,. Has ()will tell you whether something is in the set, and so on. I suspect this might be why nobody noticed.)

TypeScript 3.5 made a change inlib.es 2015 .iterable.d.tsthat had the effect of removing theany, and the generic change described above made it now inferunknown.

This change ended up being tedious to fix, because the eventual type errors sometimes were pretty far from the actual problem. For example, in this code:

class C {   gather () {     let s=new Set ();     s.add ('hello');     return s;   }   use (s: string []) {…}   demo () {     this.use (Array.from (this.gather ()));   } }

You get a type error down by theArray.frombut the required fix is ​​at thenew Set (). (I might suggest the underlying problem in this code is relying on inference too much, but the threshold for “too much” is difficult to communicate to users.)

Suggestion: we are surprised nobody noticed this, since it broke our code everywhere. The only thing worth calling out here is that it seems like nobody made this change intentionally – it’s not in the breaking changes page, andthe bug I filed about itseems to mostly have prompted confusion. Theactual changethat I think changed what overloads got picked looks harmless . Perhaps the main lesson we learned here is that we needed to discover this earlier and provide this feedback earlier.

PS: It also appearsnew Map ()may have the same problem withany.

Conclusion

I’d like to emphasize we are very happy with TypeScript in general. It is our hope that the above critical feedback is useful to you in your design process for future development of TypeScript.

      


Read More
Payeer

What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

5 Trigger Words Trump Uses to Shock Stock Market Moves – CCN.com, Crypto Coins News

5 Trigger Words Trump Uses to Shock Stock Market Moves – CCN.com, Crypto Coins News

hongkonggong / beyond-lennon-walls, Hacker News

hongkonggong / beyond-lennon-walls, Hacker News