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

int32 type returned by bitwise operators #32188

Open
5 tasks done
calebmer opened this issue Jul 1, 2019 · 10 comments
Open
5 tasks done

int32 type returned by bitwise operators #32188

calebmer opened this issue Jul 1, 2019 · 10 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@calebmer
Copy link

calebmer commented Jul 1, 2019

Search Terms

bitwise operators, integer type, int32

Suggestion

Add an int32 subtype for number returned by TypeScript from bitwise operators.

function coerce(n: number): int32 {
  return n | 0;
}

JavaScript bitwise operators coerce arguments to int32 and the return type of those operators will always be an int32.

This would not be a breaking change since int32 would be a subtype of number. Only bitwise operations would change to return int32s.

EDIT: I now recommend calling the type BitwiseInt32 so that user’s won’t see the type as a generic integer type.

Use Cases

Helps applications which are trying to take advantage of JavaScript implementations int32 optimizations. An application could ask for an int32 parameter to force coercion with n | 0.

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.
@MartinJohns
Copy link
Contributor

Duplicate of #195.

@calebmer
Copy link
Author

calebmer commented Jul 1, 2019

This is a much more limited proposal. I only care about bitwise operators. I want to use the type system to force developers to coerce to an int32 with n | 0 in performance critical code.

I wouldn’t advise most developers use this feature.

To that end, I’d instead recommend naming the type BitwiseInt32 to discourage usage of this type as a general integer type like #195 wants.

@calebmer
Copy link
Author

calebmer commented Jul 1, 2019

In JavaScript, you can safely represent integers up to 253-1. However, for bitwise operators you only have a range of integers -231 through 231-1. If there were to be an int type you might want it to cover the full JavaScript safe integer range. However, for bitwise operations specifically you want a tighter range.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jul 1, 2019
@RyanCavanaugh
Copy link
Member

I guess the problem here is that people would generally expect *, -, and + to follow int32 + int32 = int32, even though that is definitely not something that could be guaranteed.

It seems like you really just want a brand here?

@calebmer
Copy link
Author

calebmer commented Jul 1, 2019

It’s actually desirable that the type for BitwiseInt32 + BitwiseInt32 = number because that forces the user to then coerce with | 0. So if you want to write a type safe addition you’d need:

function addInt32(x: BitwiseInt32, y: BitwiseInt32): BitwiseInt32 {
  return x + y | 0;
}

Which has the right properties if you’re looking to do int32 math in JavaScript.

@calebmer
Copy link
Author

calebmer commented Jul 1, 2019

That’s why I recommend calling the type BitwiseInt32 instead of int32. You don’t want the user to expect int32 + int32 = int32. If you make the name more ugly, then hopefully user’s won’t attach their assumptions to what the type should do.

@nmain
Copy link

nmain commented Jul 2, 2019

What about a uint32 type for >>> 0?

@shicks
Copy link
Contributor

shicks commented Nov 5, 2019

Would #33290 help in defining/implementing this? I'd like to see a handful of these types defined. At the very least, int, uint32, and int32. Unlike #195, I would argue that these should definitely not affect the emit (a non-starter). This would enable a number of things:

  1. I would expect int + int = int but not int32 + int32 = int32. The latter should fall back on the former.
  2. Better safety and IDE type hints for the results of | 0 or >>> 0 and others.
  3. With int (and maybe other similar types for other functions), Number.isInteger could be made into a type guard, and then the false branch of if (Number.isInteger(stringOrNumber)) would correctly still include number (see Number.isInteger and others should accept any and act as type guards #21199).

@make-github-pseudonymous-again
Copy link

make-github-pseudonymous-again commented Sep 29, 2021

I would like #195. I want to use int32 for performance reasons: I have one use case where numbers up to 2**31-1 is enough (array indexes, I know these can go up to Number.MAX_SAFE_INTEGER but I do not care about handling numbers that large), and where coercing parameters and the output of every arithmetic operation by adding | 0 (for instance, ++i ~ (i = i+1|0)) yields significant time saving and produces compiled code almost twice smaller even though more subroutine calls are inlined (10KB instead of 17KB, I used Indicium to witness that).

I think what I have experienced can be extrapolated. I have the feeling that this addition would make basic algorithms in JavaScript much less resource consuming (if you agree to limit yourself to inputs of reasonable size). This claim would need to be evaluated.

I agree this example use case is a niche. I do not want int32 to be the new number. Even more so if it means errors due to implicit integer division or integer overflow.

Indeed a workaround is to have a branded int32 type, a coercion function, and arithmetic functions. But I have sufficiently many arithmetic operations in my code that I fancy leaving the responsibility to TypeScript would not be a luxury. I am probably wrong. Maybe all we need is a tiny library which includes said type and functions.

Nervetheless, if this responsibility would be shifted to TypeScript, I can see two problems in code emission:

  • It's not just about dropping types anymore, you need to emit transformed code (maybe this then falls out of scope of TypeScript).
  • What do you do with inferred types? Do you require explicit types for the transform to kick in?

PS: If going the workaround route then explicit inlining hints would be relevant (see #661 for instance). The current v8 is excellent at inlining what needs to be inlined, I do not deny it, I have seen it in action and it is impressive. That does not contradict wanting more control on code emission. The better argument would be that implementing this takes time both for conception and maintenance, and has limited applicability. If we want to argue code size, replacing a(x,y) by x+y|0 uses one less character but a(a(x,y),z) would need parentheses (x+y|0)+z|0 and now we are equal, inlining a(a(a(x,y),z),w) emits something with one more character. Not sure if output size is relevant. The case (i = i+1|0) would be better served by a macro or a code transform as you cannot wrap the assignment in a function call but if we cannot have that then (i = f(i)) already saves one character because the right operand is constant. Hence we cannot argue inlining will always save bytes, nor can we argue it will always use more. A reasonable argument against implementing inlining hints in TypeScript is that minifiers can do that job, giving even more control (although, at the moment, terser produces an IIFE for some (each?) inlined call, and there is no way to guide the process).

@Rudxain
Copy link

Rudxain commented Dec 8, 2022

I'd like to point out the fact that JS "canonically" has these types: Int32Array and Uint32Array. Sometimes, I must use these arrays as "wrappers" for those primitive types, because they offer "implicit coercion on overflow". But having to do arithmetic on a constant index of 0 is too awkward:

// BEGIN BOILERPLATE
interface FixedInt32Array<T extends number> extends Int32Array { 
	length: T;
}
interface Int32ArrayConstructor {
	new<T extends number>(length: T): FixedInt32Array<T>;
}

interface FixedUint32Array<T extends number> extends Uint32Array { 
	length: T;
}
interface Uint32ArrayConstructor {
	new<T extends number>(length: T): FixedUint32Array<T>;
}

type Int32 = FixedInt32Array<1>;
type Uint32 = FixedUint32Array<1>;
// END BOILERPLATE

const
    // verbose initialize to 0
    n: Uint32 = new Uint32Array(1),
    // can't do `Uint32Array([3])`, because of type signature
    m: Uint32 = new Uint32Array(1);
// values still mutable, despite `const`
m[0] = 3


const addU32 = (a: Uint32, b: Uint32): Uint32 => {
    // can't reuse `a`, because of side-effects
    const ret: Uint32 = new Uint32Array(1);
    // `a[0] + b[0]` can overflow, so we use `ret` for safety
    ret[0] = a[0] + b[0];
    return ret;
}

// annoying `[0]`, everywhere
console.log(addU32(n, m)[0]);

( some code borrowed from #18471 (comment) )

By that point, devs would just import a package such as this. Or use something like

Number(BigInt.asUintN(32, BigInt(x + y)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants