Closed ivogabe closed 6 years ago
GraphQL supports signed 32 bit integers and doesn't address other integer types. IMO that's a great starting point. http://graphql.org/learn/schema/
@benatkin if TC39 BigInt ever ships, there's a pretty big drawback to defining integer types based on number
: TC39 BigInt is a separate type at the VM level, and one which for which an exception is raised if mixed number
/BigInt
arithmetic is attempted.
If people were to add int32
types to their type declarations for untyped JavaScript code which are actually applying constraints to number
, and TypeScript were to switch to TC39 BigInts as the underlying JavaScript backing type for int32
, then all code which has int32
in their type declarations would be broken.
The TC39 BigInt proposal is now stage 3 and I think stands a pretty decent chance of eventually shipping, so I think it's probably best to "reserve" any potential integer types for ones which are actually integers at the VM level.
BigInt is scheduled for TS 3.0 (#15096) 🎉! Adding different syntax for "double based integers" as proposed in this issue would be confusing, so I think we can close this. See also #195.
This proposal introduces four new number types:
int
,uint
,int<N>
anduint<N>
, where N is any positive integer literal. Anint
is signed, it can contain negative integers, positive integers and zero. Anuint
is an unsigned integer, it can contain positive integers and zero.int<N>
anduint<N>
are integers limited toN
bits:int<N>
can contain integers in the range-Math.pow(2, N-1) ... Math.pow(2, N-1) - 1
.uint<N>
can contain integers in the range0 ... Math.pow(2, N) - 1
This proposal doesn't use type information for emit. That means this doesn't break compilation using
isolatedModules
.Note: since JavaScript uses 64 bit floating point numbers, not all integers can be used at runtime. Declaring a variable with type like
uint<1000>
doesn't mean it can actually store a number likeMath.pow(2, 999)
. These types are here for completeness and they can be used in type widening as used in type inference of binary operators. Most languages only support integer types with 8, 16, 32 and 64 bits. Integers with other size are supported because they are used in the type inference algorithm.Goals
References
Ideas were borrowed from:
Overview
int
anduint
are both subtypes ofnumber
. These types are the easiest integer types. They are not limited to a certain amount of bits.int<N>
anduint<N>
are subtypes ofint
anduint
. These types have a limited size.In languages like C, the result type of an operator is usually the same as the input type. That means you can get strange behavior, and possibly bugs:
To mimic that behavior we would need to add type converters everywhere in the emitted code, and those heavily rely on type information. We don't want that, so instead we widen the return types. That means there is no runtime overhead. For example, adding two values of type
uint<8>
would result inuint<9>
. To assign that to auint<8>
, you can use an conversion function, likeuint<8>(x)
. That function converts x to anuint<8>
.This design means that operations like
x++
,x += 10
andx /= 2
are not allowed, as they are not safe. Instead you can usex = uint<8>(x + 1)
andx = uint<8>(x / 2)
.int
anduint
allow more operations. Since they are not defined with a limited size,x++
andx += 10
are allowed.x--
is only allowed on anint
, as anuint
might become negative. Below is an overview of which operators are allowed on which number types.Since emit isn't based on type information, integers can be used in generics. They can also be used in unions.
Assignability
int
anduint
are assignable tonumber
uint
is assignable toint
int<N>
is assignable toint
andnumber
uint<N>
is assignable toint
,uint
andnumber
int<N>
is assignable toint<M>
iff N <= Muint<N>
is assignable touint<M>
iff N <= Mint<N>
is not assignable touint<M>
for all N, Muint<N>
is assignable toint<M>
iff N < MInfinity
,-Infinity
andNaN
are not assignable to any integer type.If a type is not assignable to some other type, you can use a normal cast (
<int<8>> x
orx as int<8>
), which has no impact at runtime, or a cast function (int<8>(x)
).Cast function
A cast function takes a number and converts it to the target integer type.
Syntax:
Note: even though an
int
doesn't have a fixed size, we use the 32 bit cast function as that's easy and fast JavaScript.Semantics:
This gives the same behavior as type casts in languages like C. If the operand is not a number, TS should give a compile time error. Emit should succeed (unless
--noEmitOnError
is set), the operand should be converted to a number at runtime, using same semantics as+x
.undefined
andnull
should also be converted the same way.Implemented in TypeScript:
These functions are not always used in the generated code. When
n <= 32
, these functions are not needed. The generated code:If
n === 32
:If
n < 32
,Question: can we make the emit ofSolved it using http://blog.vjeux.com/2013/javascript/conversion-from-uint8-to-int8-x-24.htmlint<n>(x)
better? The current isn't very nice and performs bad.If
n > 32
:__castToUint
and__castToInt
are the functions above, emitted as helper functions.You can only use these cast functions in call expressions:
We cannot solve that with helper functions, as
int
wouldn't be equal toint
if they don't come from the same file when using external modules. Instead we should dissallow this.Type inference
Introducing integer types can break existing code, like this:
But we do want to type
1
as anint
:There are several options:
In option two we infer to
int
, aslet a = 0
would otherwise infer touint<1>
, which would mean that the variable can only contain 0 and 1.Examples of option 3:
A literal will be infered to the smallest integer type that can contain the number. Examples:
Operators:
Operators should always infer to the smallest integer type that can contain all possible values.
Certain assignment operators are not supported on integer types. In short: Let
Op
be an operator.x Op= y
is allowed iffx = x Op y
is allowed. That means that the following operators are not supported and usage will give an error:Breaking changes
Type inference can change depending on how it will be implemented. Also changing existing definitions can break things:
Changing the definition of the
length
property to auint
would break this code, though I don't think this pattern will be used a lot. Such problems can easily be fixed using a type annotation (in this caselet length: number = arr.length;
).Questions
number
? Thus,let x: int
vs.let x: number.int
int<8>
/number.int<8>
orint8
/number.int8
?Can we make the emit ofSolved using http://blog.vjeux.com/2013/javascript/conversion-from-uint8-to-int8-x-24.htmlint<n>(x)
(n < 32) better? The current looks and performs bad.undefined
andnull
be assigned to an integer type? The conversion functions convert them to 0. Allowingundefined
would mean thatint + int
could beNaN
(if one operand isundefined
), whileNaN
is not assignable to anint
. I'd say thatundefined
andnull
shouldn't be assignable to an integer type, and that declaring a variable (also class property) with an integer type and without an initializer would be an error.All feedback is welcome. If you're responding to one of these questions, please include the number of the question. If this proposal will be accepted, I can try to create a PR for this.