Skip to content

The type of bitwise operations on 'any' operands should be 'number | bigint' #42125

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

Open
fasttime opened this issue Dec 27, 2020 · 12 comments
Open
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros Suggestion An idea for TypeScript

Comments

@fasttime
Copy link

fasttime commented Dec 27, 2020

Bug Report

πŸ”Ž Search Terms

  • bigint
  • bitwise

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about bigint.

⏯ Playground Link

Workbench Repro

πŸ’» Code

//@target: ES2020
const v1 = 1n as any;
const v2 = 2n as never;
const v3 = v1 | v2; // v3 should be typed (number | bigint), but is number

πŸ™ Actual behavior

The result of the bitwise operations &, |, ^ and ~ is assumed to be a number when all the operands have unspecified type, like any or never.

πŸ™‚ Expected behavior

The result of bitwise operations with unspecified type operands should be number | bigint.

@MartinJohns
Copy link
Contributor

The result of bitwise operations with unspecified type operands should be number | bigint.

That wouldn't make your example 2 valid. You could still call bitwiseAnd with a bigint and a number, resulting in an error. Same issue as #12410.

@fasttime
Copy link
Author

@MartinJohns You must be right. I fixed my sample code, thanks!

@MartinJohns
Copy link
Contributor

I fixed my sample code

You really didn't, because you can't. There's no way to currently type this in a safe way, as mentioned in #12410 already. You would need #27808 for this.

typeof at the type level does not refer to the runtime type of variables. In your updated example 2 typeof a is just a synonym for I. A very common misconception of I extends number | bigint is that I can only be number or bigint, but it can be number | bigint as well.

const a1 = 123; // number
const a2 = 456n; // bigint
bitwiseAnd(a1, a2); // calling with number and bigint.

// Or alternatively
bitwiseAnd<number | bigint>(123, 456n);

@fasttime
Copy link
Author

In that case, I'll leave that part out of my example. Why am I using TypeScript anyway?

@MartinJohns
Copy link
Contributor

Related: #41741 (possibly a duplicate)

@fasttime
Copy link
Author

Clearly TypeScript thinks that certain operators can produce nothing but a number, which used to be true (i.e. -value), but no longer holds in times of bigint.

@MartinJohns
Copy link
Contributor

The issue I last linked was for TypeScript 4.2.0, bigint already exists for quite a while longer. The decision made in that issue does not refer to a state that "used to be true", but to the current version.

From the TypeScript Design Goals:

Non-goals
3. Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.

I would guess that in the majority of cases number is an appropiate type, and having it result in number | bigint would just cause additional headache. Less correctness, more productivity. And IMO when you use bitwise operands on any or never you already left the world of type-safety anyway.

@fasttime
Copy link
Author

Well, I wasn't talking about the state of the dev version of TypeScript, but the current version of ECMAScript, or ES2020, where bigints are introduced as part of the language (JavaScript, not TypeScript). Certain expressions that could previously only produce number values now produce bigint values.

It is well possible that the current TypeScript behavior with respect to the typing of such expressions strives to enhance productivity at the expenses of correctness. I would say though that legacy compatibility also needs some consideration here, as changing the current behavior would break existing code.

@andrewbranch
Copy link
Member

The result of the bitwise operations &, |, ^ and ~ is assumed to be a number when all the operands have unspecified type, like any, never, number | bigint, etc.

Importantly, this statement is false insofar as it includes number | bigint. number | bigint is not a valid operand for any binary bitwise operator, and when applied to a unary bitwise operator (~), the result is correctly typed number | bigint.

It seems like #41741 intended to suggest what you are suggesting, but incorrectly asserted that an expression like -x | y where x: any and y: bigint should not be an error. We now disallow this because we say -x is a number. If we were to call it a number | bigint, the expression would still be an error because it still might be a number, which would throw.

It’s possible that the incorrect formulation of that issue caused it to be dismissed without really considering the suggestion here. I tend to agree with @MartinJohns that this would be a huge pain, and the existing workarounds of β€œavoid direct unchecked manipulation of any” or β€œannotate variables as number | bigint when they are inferred as number but you know they might be bigint” are pretty good workarounds. It is possibly the sort of thing we’d add a pedantic flag for, but only after a significant showing of public support for it.

@andrewbranch andrewbranch 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 Dec 28, 2020
@andrewbranch andrewbranch changed the title The type of bitwise operations with unspecified operands should be (number | bigint) The type of bitwise operations on 'any' operands should be 'number | bigint' Dec 28, 2020
@typescript-bot typescript-bot added the Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros label Dec 28, 2020
@andrewbranch
Copy link
Member

I would also say that maybe you shouldn’t be allowed to use never in any kind of bitwise/arithmetic operation, but that seems to be its own weird problem

declare let a: never;
const x = a + a; // number, no error
const y = "a" + a; // no error

@fasttime
Copy link
Author

@andrewbranch you are correct, I removed the mention of number | bigint from the list of operand types that cause the issue to show up. Not sure what other types are affected besides any and never.

@serbanghita
Copy link

Here is a relevant recent example:

// This works
type Bitmask = number | bigint;

export function toggleAllBits(bitmask: any): Bitmask {
    const oneBit = (typeof bitmask === "bigint" ? 1n : 1) as typeof bitmask;
    let start = oneBit;
    while(start <= bitmask) {
        bitmask ^= start;
        start = start << oneBit;
    }
    return bitmask;
}

// Change the function's  signature to:
export function toggleAllBits(bitmask: Bitmask): Bitmask {
// throws TS2365: Operator '^=' cannot be applied to types 'number | bigint' and 'number | bigint'.

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 Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants