Skip to content

Manually widen a type for conditional/mapped type #31844

@krryan

Description

@krryan

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 & Pixels

Checklist

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions