-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
Search Terms
manual widen
Suggestion
I would like some keyword or other mechanism to refer to the widened version of a type, e.g. widened 42 would be number and widened 'foo' would be string. Importantly, widened 42 & { foo: 'bar' } would be number & { foo: 'bar' }.
Use Cases
Our project uses a ton of branded primitives, which are accomplished by casting a number or string to number & Branding or string & Branding. We use this for all sorts of things: we have number & Pixels to indicate that a number is in pixels (and to force numbers passed to a function expecting pixels to use a number branded as pixels), we use string & Url to indicate a string is a URL, and so on.
We also have functions that operate on these types in a generic fashion, where we want to retain the branding. For example,
declare function plus<N extends number>(a: N, b: N): N;This plus function is supposed to make sure we are adding pixels to pixels (or whatever unit), and retain the fact that their sum is also a number of pixels.
The problem comes in when we also have const offset = 5 as 5 & Pixels;. We define it as having that literal value for convenience (mostly, it shows up in Intellisense), and we have quite a few of these. More relevantly, the implementations of times and dividedBy (which I’m avoiding putting here as they are vastly more complex to cover canceling out units) can and should be able to take just plain numbers, but when I call times(offset, 2) it is inferred as times<Pixels, 2> and the return value retains the 2 even though obviously the runtime value is almost-certainly not going to be 2.
I asked for a solution to this on Stack Overflow, and was informed the only solution was to manually recreate the type by covering all of the potential brandings—which I basically have done, except that in the end there needs to be a generic overload, because we have a lot of situations where these functions are called by functions/classes that are also generic, and so only have N extends number, so the only overload available is the final one—which just retains whatever N was, even if N was a literal. I want to use widen N instead.
Examples
const c = 5 as const;
const w: widened typeof c = 3;
function plus<N extends number>(a: N, b: N): widened N {
return a + b;
}
class Pixels { private '__ in': 'pixel'; }
const offset = 5 as 5 & Pixels;
declare const width: Pixels;
const offsetWidth = plus(width, offset); // Pixels, not 5 & PixelsChecklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.