Trying to solve #6477, but without implicit open
## Rationale
As explained… in #6477, we have the "infix explosion" problem. To relax it, I added some specialization to both type inference and primitive inspired by F#
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/arithmetic-operators#operators-and-type-inference
The idea is, to extend the type inference rules for primitive operations defined in Pervasives to specialize them for predefined types but fall back to `int` if they cannot be inferred.
```res
let t1 = 1 + 2 // => external \"+": (int, int) => int = "%addint"
let t2 = 1. + 2. // => external \"+": (float, float) => float = "%addfloat"
let t3 = "1" + "2" // => external \"+": (string, string) => string = "%string_concat"
let t4 = 1n + 2n // => external \"+": (bigint, bigint) => bigint = "%addbigint"
let fn1 = (a, b) => a + b // => external \"+": (int, int) => int = "%addint"
let fn2 = (a: float, b) => a + b // => external \"+": (float, float) => float = "%addfloat"
let fn3 = (a, b: float) => a + b // => external \"+": (float, float) => float = "%addfloat"
let inv1 = (a: int, b: float) => a + b // => external \"+": (int, int) => int = "%addint"
// ^ error: cannot apply float here, expected int
```
This doesn't make a breaking change since the existing operations are already defined on `int`.
However, we can introduce them as unified operators for the new codebase. They are still safe, as they are always specialized and not polymorphic.
Specialized operators:
| Op | Primitive | supported types | ready? |
|----|----|----| ---- |
| `~+` | `%plus` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `~-` | `%neg` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `+` | `%add` | `int`, `float` , `bigint`, `string` | :heavy_check_mark: |
| `-` | `%sub` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `*` | `%mul` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `/` | `%div` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `mod` | `%mod` | `int`, `float`, `bigint` | :heavy_check_mark: |
| `%` | `%mod` | `int`, `float`, `bigint` | #7152 |
| `**` | `%exp` | `int`, `float`, `bigint` | #7153 |
| `&` | `%bitand` (formerly `%land`) | `int`, `bigint` | |
| `\|` | `%bitor` (formerly `%lor`) | `int`, `bigint` | |
| `^` | `%bitxor` (formerly `%lxor`) | `int`, `bigint` | |
| `~` | `%bitnot` (formerly `%lnot`) | `int`, `bigint` | |
| `<<` | `%lsl` | `int`, `bigint` | |
| `>>` | `%asr` | `int`, `bigint` | |
| `>>>` | `%lsr` | `int` | |
| `abs` | `%abs` | `int`, `float`, `bigint` | |
| `min` | `%min` | `int`, `float`, `bigint`, `string` | |
| `max` | `%max` | `int`, `float`, `bigint`, `string` | |
## Side note
This PR doesn't attempt to add custom ops or polymorphic ops, but that's the specialized custom ops [already possible](https://github.com/rescript-lang/rescript-compiler/issues/6477#issuecomment-1820628940) by opening a module.
Polymorphic ops should be deprecated. They are unpredictable and inefficient, as explained in #7031. So, in future versions, comparisons will behave consistently with this change. It will be a breaking change, but we don't have a better alternative yet.