question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Proposal: Operator overloading and primitive type declarations

See original GitHub issue

Suggestion

šŸ” Search Terms

Operators, operator types, operator overloading

āœ… Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScriptā€™s Design Goals.

ā­ Suggestion

Introduce a way to show what types can/cannot be gained from using an operator on a value of a type.

No, I am not asking for operations to be replaced with functions or such, like many other issues have asked for, I am merely asking for a way to show what types that an operation will yield when performed between two types.

Currently, there is no way to describe operator types in TS.

šŸ“ƒ Motivating Example

I donā€™t have an idea for the syntax, but Iā€™ll introduce a partial syntax to showcase the idea here.

Letā€™s imagine that TS allowed us to declare primitive types via, say, a primitive keyword:

primitive string {
    +(lhs: string, rhs: string) => string;
    +=(lhs: string, rhs: string) => string;
}

primitive symbol {
    +(lhs: symbol, rhs: never) => never;
}

primitive number {
    +(lhs: number, rhs: number) => number;
    -(lhs: number, rhs: number) => number;

    +(lhs: number, rhs: string) => string;
}

This is already valid TS:

let x: string = 2 + "";
let y: number = 2 + 2;

This suggests that TS already has the notion of overloaded operators that I have suggested.

Note that operators may never have a body, as TSC is not allowed to emit runtime code for the operations.

Now, TS doesnā€™t presently allow us to declare primitives, but operations between objects always throw the error:

Operator ā€˜{op}ā€™ cannot be applied to types ā€˜{object}ā€™ and ā€˜{object}ā€™.

And generally, yes, that is a good thing, but is it always? Take this example:

const result: string = new String("foo") + new String("bar");

I know that the ES abstract ToPrimitive will be called on both of these objects, resulting in the primitive string value contained within. Run the code yourself, it will result in "foobar", and we know this, so letā€™s tell TSC that too! But, instead of the type string being the result of the concatenation operator, we get this:

Operator ā€˜+ā€™ cannot be applied to types ā€˜Stringā€™ and ā€˜Stringā€™.

Letā€™s say that we were using a type that becomes a number, ex: WebAssembly.Global:

const options = {
    value: "i32",
    mutable: false
};
const x = WebAssembly.Global(options, 100);
const y = WebAssembly.Global(options, 1);

const z: number = x + y; // 101

That string example could could now be something like this:

class Str extends String {
    +(lhs: Str, rhs: Str) => string; // just an operation, and it's types

    // !(lhs: Str) { return !this; } // I'm not proposing runtime operations, that is out of scope for TS!
}

const result: string = new Str("foo") + new Str("bar"); // no error: return type is "string" primitive!

Of course, as with anything else, this can be misused, but itā€™s no worse than the already existing 2 + "" semantics.

šŸ’» Use Cases

This can likely solve issues such as https://github.com/microsoft/TypeScript/issues/28682 solely via user-implemented types!

If we could declare opaque types, such as those mentioned in https://github.com/microsoft/TypeScript/issues/15408 or https://github.com/microsoft/TypeScript/issues/40075, one could do stuff like, say, creating a NaN type, and stricter number types, all without runtime overhead and erasable types.

Toss in throw types and we can get some good error messages out of it too:

primitive NaN {
    +(lhs: NaN, rhs: number) => throw `One of the operands is possibly NaN, this arithmetic may be unsafe`
}

primitive strict_number {
    /(lhs: strict_number, rhs: strict_number) => strict_number;
    /(lhs: strict_number, rhs: 0) => NaN; // could be 'never' or throw
    **(lhs: strict_number, rhs: strict_number) => NaN | strict_number;
}

declare function isNaN(n: number): n is NaN;

let x: strict_number = 42;
let y: strict_number = x / 0; // Error type 'NaN' is not assignable to 'strict_number'

let a = x ** x;

if ( !isNaN(a) ) {
    // ... use a like normal number
} else {
    // oh no!
}

Another use, working with pointers into, say, WebAssembly memory, it usually makes no sense to do something like raising it to an exponent, and this could allow us to scope what operations are permitted. Before:

// wasm_func(): number
// wasm_func2(n: number): void
const index: number = wasm_func();
wasm_func2(index ** 3);

after:

primitive pointer /*extends number?*/ {
    +(lhs: Pointer, rhs: number) => pointer;
    -(lhs: Pointer, rhs: number) => pointer;

    +(lhs: Pointer, rhs: pointer) => number;
    -(lhs: Pointer, rhs: pointer) => number;
}
// wasm_func(): pointer
// wasm_func2(n: pointer): void
const index: pointer = wasm_func();
wasm_func2(index ** 3); // TS error: operation "**" cannot be performed between types "pointer" and "number"

There will have to be some TSC enforced rules in order for it to actually be useful, ex: the return type of + must extend number | string | bigint, because nothing else could be possible.

If the primitive type idea is too radical, it could be completely decoupled from the operator overloading, so that I may perform my object arithmetic with the safety of TS. šŸ˜ƒ


Also, eventually, TS may have to implement this anyways: https://github.com/tc39/proposal-operator-overloading

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:15
  • Comments:9 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
Judahhcommented, May 11, 2022

Work with packages like bignumber.js and dinero.js would improve significantly.

0reactions
iliazeuscommented, Feb 18, 2022

I really think the operator overloading should be detached from the ā€œprimitiveā€ proposal, since ā€œprimitivesā€ can be implemented using, e.g., branded types.

A more conservative operator overload syntax will perhaps be something like:

declare operator "+"(lhs: Foo, rhs: Foo): Foo;

Some of my (current) use cases include:

  • measurement units (in my case, different currencies)
  • number types of NativeScript
  • migrating JS code that (ab)uses weak typing with expressions like 1 + true
Read more comments on GitHub >

github_iconTop Results From Across the Web

Operator overloading in JavaScript
Operators can be overloaded for one operand being a new user-defined type and the other operand being a previously defined type only in...
Read more >
C++ Operator Overloading with Primitive Types - Stack Overflow
The error is: "No operator "-=" matches these operands. operand types are int -= damage. I have also tried using the - operator...
Read more >
Avoiding overloading - Google Groups
In my original "implements" proposal, I accepted it as an implication of using operators to define type classes, because I thought (and still...
Read more >
class-letter.txt
This proposal suggests a new approach to dealing with overloading. ... value must be an instance of the corresponding type in the OVERLOAD...
Read more >
Google C++ Style Guide
Pairs and Tuples; Inheritance; Operator Overloading; Access Control; Declaration Order. Functions. Inputs and Outputs; Write Short FunctionsĀ ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found