Repository navigation
Proposal: int types #4639
Description
Activity
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensus
on Sep 4, 2015 For the more common cases of
uint8,uint16,uint32,int8,int16,int32there are simple tricks/bit "twiddles" that can make the truncations much faster/simpler. I tried to make them fit in one statement (sacrificing a tiny bit of performance in some), to make them better candidates for inlining (either through the JS runtime or TypeScript itself).Unsigned:
function toUint8(num: number): uint8 { return num & 255; } function toUint16(num: number): uint16 { return num & 65535; } function toUint32(num: number): uint32 { return (num | 0) >= 0 ? (num | 0) : (num | 0) + 4294967296; }
Signed:
function toInt8(num: number): int8 { return (num & 255) < 128 ? (num & 255) : (num & 255) - 256; } function toInt16(num: number): int16 { return (num & 65535) < 32768 ? (num & 65535) : (num & 65535) - 65536; } function toInt32(num: number): int32 { return num | 0; }
These functions would truncate to the least significant
8,16or32integer bits of any floating point number. Numbers outside of the range[-2^52..2^52 - 1]would still work but would be naturally limited in resolution.I've ran some automated testing for these with millions of random values against the expected truncation behavior in typed arrays and they seem to give exactly similar results including when the number is
NaN,-Infinityand+Infinity,null,undefined, or any other type, including astringor anobject.anything | 0andanything & numalways resolve to0in these cases [with the special exception ofbooleanwheretrueis automatically converted to1andfalseto0].
There is an alternative method to do this that may or may not be faster (depending on the runtime), but would require runtime support for typed arrays (only available in IE10+):
Unsigned:
uint8Array_dummy = new Uint8Array(1); uint16Array_dummy = new Uint16Array(1); uint32Array_dummy = new Uint32Array(1); function toUint8(num: number): uint8 { uint8Array_dummy[0] = num; return uint8Array_dummy[0]; } function toUint16(num: number): uint16 { uint16Array_dummy[0] = num; return uint16Array_dummy[0]; } function toUint32(num: number): uint32 { uint32Array_dummy[0] = num; return uint32Array_dummy[0]; }
Signed:
int8Array_dummy = new Int8Array(1); int16Array_dummy = new Int16Array(1); int32Array_dummy = new Int32Array(1); function toInt8(num: number): int8 { int8Array_dummy[0] = num; return int8Array_dummy[0]; } function toInt16(num: number): int16 { int16Array_dummy[0] = num; return int16Array_dummy[0]; } function toInt32(num: number): int32 { int32Array_dummy[0] = num; return int32Array_dummy[0]; }
Anyway, this should be used as a reference to test the correctness of the truncations.
[Edit: I retested the first version and it does seem to return
0forNaN,Infinity,undefinedetc. as expected, the problems happened in previous versions of the functions]Reacted by esromneb and flexiworldivogabe commented
on Sep 5, 2015 ContributorAuthorMore actionsI've changed the emit for
int<n>(x)wheren < 32. It now uses bit shifting as explained here. This is probably the best solution for question 3.Rotem Dan (@rotemdan) Those conversions exist for all integer types up to 32 bits. You can see them all in the section "Cast function". TS should inline these functions, and browsers can optimize these constructs. The
castToIntandcastToUintare functions that demonstrate the expected behavior, the alternatives (likex | 0) follow that behavior.Great find! so let's try to summarize the simplest, most efficient implementations we have so far for the most important cases:
Unsigned:
- uint8:
num & 255 - uint16:
num & 65535 - uint32:
num >>> 0
Signed:
- int8:
(num << 24) >> 24 - int16:
(num << 16) >> 16 - int32:
num | 0
I ran them through the automated tests (1M random FP values up to about +/- 2^71) and they seem to work correctly.
It seems reasonable to have the behavior standardized against assignments to typed arrays so these should all yield
0forNaN,undefined,stringetc. not sure aboutboolean(right now some of these will give1fortruebut that's a really minor detail).[Edit: I confirmed
typedArray[0] = truewould set the value to1, so there is no need for a correction there.]Another minor detail: I'm not sure if adding
| 0is needed for to be recognized as integers by asm.js (I'm not very familiar with it), I assume the JS runtime could be "smart" enough to recognize them?- uint8:
ivogabe commented
on Sep 5, 2015 ContributorAuthorMore actionsRotem Dan (@rotemdan) Most JS engines can optimize
| 0very well, but other binary operators are less optimized. I haven't measured the difference though. NaN, +/-Infinity, undefined and null should be converted to 0, strings are first converted to a number (eg "120" -> 120, "1.5" -> 1.5 -> 1). For booleans, true is converted to 1 and false to 0. That behavior is consistent between the proposed casts and typed arrays.You mentioned that operations like
x++,x += 10andx /= 2are not allowed without a cast. I understand the desire for safety but I still think that having an automatic cast/truncation on assignment is still reasonable for practical purposes (and is actually a useful/desirable thing to have for some applications).I mean that:
let num: uint8 = 255; num = num + 1;
Would emit:
var num = 255; num = num + 1; num &= 255;
The automatic casts would only happen on assignments, "anonymously typed" intermediate results would not be effected:
let num: uint8 = 255; num = (num * num) + (num * 2);
Still emits only a single truncation:
var num = 255; num = (num * num) + (num * 2); num &= 255;
The reason I wanted to "optimize" and simplify the casts (including the logic for them) as much as possible was because I wanted to make them extremely cheap for these purposes (In terms of performance, I think the operations turned up cheap enough to be called very frequently, including in tight loops).
Since typed arrays automatically truncate on assignment and don't error on overflows or even on type mismatches, it seems like a reasonable compromise to have a somewhat more permissive logic. Not having these automatic casts would make it difficult to work with these types in practice (in some cases an explicit cast would be needed at every single line).
It is still possible to limit automatic casts/truncations to only work between integer types, though they would not be required to be compatible:
let num: uint8 = 255; num = num * 23; // OK, truncated to least significant 8 bits on assignment. ("modulo 2^8"). num = num * 23.23423425; // Error, incompatible numeric types num = <uint8> num * 23.23423425; // OK
Edit: It is possible to limit this even further to only apply to anonymous/intermediate expressions. Assignments between named variables may still be strict:
let num1: uint8 = 45; let num2: uint16 = 11533; num1 = num2; // Error: incompatible numeric types num1 = <uint8> num2; // OK
It might be possible to strengthen it back with even more restrictions. I guess there's a balance here between safety and usability.
ivogabe commented
on Sep 5, 2015 ContributorAuthorMore actionsRotem Dan (@rotemdan) The behavior you suggest would require emit based on type information. That means
isolatedModules/transpile(compilation per file in stead of one compilation of the whole project) cannot be used with integers. The key feature of this proposal is that it does not use type information for emit. That way the feature fits better in TypeScript.If you want less restrictions you can use
intoruint, which are not restricted to a specific size. You can write this:let x: uint = 0; x++;
The
intanduinttypes give less guarantees (the only guarantees are that a value is round and in case of anuint, not negative), but are easier to use. In most cases you can use these types.Ivo Gabe de Wolff (@ivogabe) Very correct.
I would think it would make more sense to just start out with four types:
int- A 32-bit signed integer. This would go in line with most of JavaScript's bitwise operators.uint- A 32-bit unsigned integer. This would account for the following cases (and a few others on the Math object):int >>> int -> uintuint >>> (int | uint) -> uintint + int -> uintint - int -> uint
float-Math.fround(x) -> floatdouble- Everything else.
This is very similar to the asm.js types, and are already heavily optimized (V8 and Spidermonkey both generate type-specific code already for non-asm.js code that rely on these types). Matter of fact, almost all the asm.js types can be represented by these types + the type system.
asm.js TypeScript voidvoidsignedintunsigneduintint`int fixnumint & uintintish`int doubledoubledouble?`double floatfloatfloat?`float floatish`float externany
Note: asm.js requires
intishto be coerced back intointoruintandfloatishto be coerced back intodoubleorfloat. TypeScript wouldn't require it, but people can still remain explicit when necessary.As for implementation, there is no realistic way to guarantee these types without relying on explicit coercions.
int -> x | 0 uint -> x >>> 0 int & uint -> x & <int & uint> y // e.g. y = 0x7fffffff float -> Math.fround(x) or a Float32Array member double -> +x // int, uint, and float are each subtypes of double // double is a subtype of number function f(x: number): int { // Error! // return x return x | 0 }
Even asm.js has to use such coercions to keep the math correct. Unlike asm.js, though, you don't have to coerce everything, so you don't have to write
expr(x) | 0a bunch of times. You also don't have to coerce your arguments, as the argument types are checked statically.Reacted by Yahiko Uzumaki, Ivan Agafonov, Jan Stoots and flexiworldivogabe commented
on Sep 6, 2015 ContributorAuthorMore actionsFloats and doubles would indeed be a good addition. For those who don't see the advantage, I'd suggest reading this: https://blog.mozilla.org/javascript/2013/11/07/efficient-float32-arithmetic-in-javascript/. I would call these types
float32andfloat64, as that's more in line with the names for typed arrays and SIMD in JS.The reason I chose to introduce more integer types is type safety.
int32 + int32isn't always anint32, so we must either add a conversion around it (implicitly), which would require type information, or widen the type to anint33. I chose the latter. The user would need to add the conversions himself. If he doesn't want that, he can use the 'unsized' integer typeint, which doesn't check the size.My proposal introduces syntactic sugar for those casts, as I'd rather write
int(x)thanx | 0anduint<8>(x)than(x | 0) & 256. Of course this can be extended to floats:float32(x)->Math.fround(x)andfloat64(x)->+x.IMPinball
int + int -> uintisn't always true, consider1 + -3 === -2Ivo Gabe de Wolff (@ivogabe) I forgot about that... Oops. ;)
And I'm aware that type casts that end up in the emit would definitely be useful. But I'm taking into account the TypeScript compiler devs generally steer away from it.
If you want to create a third party compiler (a la CoffeeScriptRedux) with that as a nonstandard extension, go for it!
Or even edit the current one to do that, that'd still be fine.
And as for the link, I read it while creating this post. (I optimized the polyfill shortly after.)
One thing that will have to be addressed with yours is right shifts with integers smaller than 32-bit. That can easily bring garbage in. And that would require those types making it into the emit.
ivogabe commented
on Sep 6, 2015 ContributorAuthorMore actionsIMPinball At first I found it strange that they didn't want to use type info, but now I think they're right about that. Using
isolatedModulescompilation times can get decreased from a few seconds to less than a second. Introducing a feature that would break that would be a very bad idea. Someone of the team can explain this better probably.I don't really like the idea of forking, it's not hard to create a fork, maintenance is usually the problem.
One thing that will have to be addressed with yours is right shifts with integers smaller than 32-bit. That can easily bring garbage in. And that would require those types making it into the emit.
I'm not sure what you mean with this, can you clarify that? Right shift (
x >> y) removes bits and is sign preserving, so it's guaranteed thatabs(x >> y) <= abs(x). Left shift doesn't have such guarantee, it should always infer toint<32>.Where the shifts can lead to unexpected behavior is this:
Let's assume only the rightmost 8 bits are used. JavaScript numbers are only 32-bit, but for the sake of brevity, let's assume they're 16-bit.
Let's try a left shift:
|-------| <-- The digits we care about 0000 0110 1100 0111 <-- x 0001 1011 0001 1100 <-- x << 2, okay if uint8 0000 0110 1100 0111 <-- original x 0000 0001 1011 0001 <-- x >> 2, naive 0000 0000 0011 0001 <-- x >> 2, correctThe example of the left shift introduces artifacts for signedness because then if you try to add, it's adding a positive. The example of the right shift is that you're literally introducing garbage into the program, like what you can get from allocating a raw memory pointer without zeroing the region out first.
Now, let's assume only the leftmost 8 bits are used. That'll address the issue of sign propagation and addition, subtraction, etc., but what about left and right shifts?
|-------| <-- The part we are using 1101 1001 1100 0000 <-- x 0110 0111 0000 0000 <-- x << 2, naive 0110 0100 0000 0000 <-- x << 2, correctNow, there's a similar garbage problem with left shifts. Simply because the other half wasn't zeroed out first.
Obviously, these are only 16-bit integers and JavaScript uses 32-bit for its bitwise operations, but the argument could easily be expanded to cover those.
In order to fix this, you need those types to correct the emit. And I didn't get into addition, subtraction, etc., because that's already been made.
As another nit, I don't like the generic syntax, because a) you'd have to special case the parser (numbers aren't valid identifiers for generics), b) they're a finite, limited set of types, not some sort of parameterized type, and c) it doesn't seem right with the rest of the language, as primitives are just a simple identifier word.
29 remaining items
Apparently, current TS already supports a subset of my proposal above:
type byte = 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255; type sbyte = -128|-127|-126|-125|-124|-123|-122|-121|-120|-119|-118|-117|-116|-115|-114|-113|-112|-111|-110|-109|-108|-107|-106|-105|-104|-103|-102|-101|-100|-99|-98|-97|-96|-95|-94|-93|-92|-91|-90|-89|-88|-87|-86|-85|-84|-83|-82|-81|-80|-79|-78|-77|-76|-75|-74|-73|-72|-71|-70|-69|-68|-67|-66|-65|-64|-63|-62|-61|-60|-59|-58|-57|-56|-55|-54|-53|-52|-51|-50|-49|-48|-47|-46|-45|-44|-43|-42|-41|-40|-39|-38|-37|-36|-35|-34|-33|-32|-31|-30|-29|-28|-27|-26|-25|-24|-23|-22|-21|-20|-19|-18|-17|-16|-15|-14|-13|-12|-11|-10|-9|-8|-7|-6|-5|-4|-3|-2|-1|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127; function takeByte(x: byte): byte { x++; return x + 1; // ~~~~~~~~~~~~~ Type 'number' is not assignable to type 'byte'. } takeByte(0); takeByte(10); takeByte(888); // ~~~ Argument of type '888' is not assignable to parameter of type 'byte'. function byte2sbyte(x: byte): sbyte { if (this) return x + 1; // ~~~~~~~~~~~~~ Type 'number' is not assignable to type 'byte'. else return x; // ~~~~~~~~~ Type 'byte' is not assignable to type 'sbyte'. // Type '128' is not assignable to type sbyte. }
The missing pieces are:
- Can't do it for the adult grown-up integers, need actual compiler support
- Explicit coercions still require type assertions (i.e.
myNumber & 0xFFdon't producebyte) — unlike ASM.js which has coercions that ensure output is always integer (or float). - Looks like TSC has a bug (see first line of
takeBytefunction above), increment operator shouldn't be allowed on literal union.
Reacted by Claudia Meadows and Byron Mejiaints would be nice. For example, consider this class:class KeyFrameManager { currentFrame:number // the rest of class exposes API for switching to different frames of a key-framed animation. }
There's currently no way to enforce that frames should be whole
numbers, which means if a developer introduces a calculation that produces a non-whole number there could be strange bugs.Let us enforce this. 👍 For example:
class KeyFrameManager { currentFrame:int // the rest of class exposes API for switching to different frames of a key-framed animation. }
This is simply a matter of enforcing program correctness for me, I don't care that
numbers are actually all the same in JavaScript.Reacted by PaulBGD, Christian d'Heureuse, Paulo Meira, Denis Tokarev, Sebastian Müller, Matt Mancini, Kelvin Steiner, flexiworld, rzvc, ivnsch and 1 moreHere is a definition file for numeric types used in AssemblyScript, an experimental compiler from TypeScript to WebAssembly, written in TypeScript: https://github.com/dcodeIO/AssemblyScript/blob/master/assembly.d.ts
This could be a good basis for the future numerical types in the official TypeScript compiler.
Also, until this time, I plan to use this definition file for my projects.Reacted by Victorien Elvinger and ExE BossYahiko Uzumaki (@yahiko00) There's also TurboScript which does this: https://github.com/01alchemist/TurboScript
That's right. I've also had a look at this project but it seems a little but more "experimental" than AssemblyScript, and its definition file is much less complete.
Yahiko Uzumaki (@yahiko00) The main difference is that TurboScript aims to be a bit more fully featured and is closer to a TS derivative rather than AssemblyScript is (which is more or less just a retargeted TS).
@isiahmeadows You are probably right since I am discovering these repos. Although, I like the idea of AssemblyScript to be a subset of TypeScript. Maybe I am wrong, but I think, in the future, this could be easier to merge AssemblyScript into the official TypeScript compiler.
Here is another project, wasm-util, from TypeScript to WebAssembly: https://github.com/rsms/wasm-util
In case you missed #15096 I think TC39 BigInt is the best way forward to get true integers in TypeScript.
This proposal would enable an arbitrary precision
integertype and could also hint to JavaScript VMs when they could use e.g. native 64-bit integers, enabling things like anint64anduint64type.The proposal is presently stage 2 and has multi-vendor support. In theory the syntax is mostly stable at this point.
There's a proposal to support it in Babel as well: babel/proposals#2
Reacted by Steven, Cecile Muller, Thomas Rognon and ExE BossI like how TypeScript follows the spec and tries not to add too much to it, but an integer type is just so fundamental. Would love minimal integer support that's based on safe integers in ES6. Here's a concise description of it: http://2ality.com/2015/04/numbers-math-es6.html
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/
Benjamin Atkin (@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 mixednumber/BigIntarithmetic is attempted.If people were to add
int32types to their type declarations for untyped JavaScript code which are actually applying constraints tonumber, and TypeScript were to switch to TC39 BigInts as the underlying JavaScript backing type forint32, then all code which hasint32in 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.
Reacted by Steven, Ádám Lippai, Denis Tokarev and ExE Bossivogabe commented
on Jun 8, 2018 ContributorAuthorMore actions- added a commit that references this issue
on Oct 3, 2018
This proposal introduces four new number types:
int,uint,int<N>anduint<N>, where N is any positive integer literal. Anintis signed, it can contain negative integers, positive integers and zero. Anuintis an unsigned integer, it can contain positive integers and zero.int<N>anduint<N>are integers limited toNbits: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) - 1This 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
intanduintare 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 ofintanduint. 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 += 10andx /= 2are not allowed, as they are not safe. Instead you can usex = uint<8>(x + 1)andx = uint<8>(x / 2).intanduintallow more operations. Since they are not defined with a limited size,x++andx += 10are allowed.x--is only allowed on anint, as anuintmight 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
intanduintare assignable tonumberuintis assignable tointint<N>is assignable tointandnumberuint<N>is assignable toint,uintandnumberint<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,-InfinityandNaNare not assignable to any integer type.If a type is not assignable to some other type, you can use a normal cast (
<int<8>> xorx 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
intdoesn'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
--noEmitOnErroris set), the operand should be converted to a number at runtime, using same semantics as+x.undefinedandnullshould 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 ofint<n>(x)better? The current isn't very nice and performs bad.Solved it using http://blog.vjeux.com/2013/javascript/conversion-from-uint8-to-int8-x-24.html
If
n > 32:__castToUintand__castToIntare 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
intwouldn't be equal tointif 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
1as anint:There are several options:
In option two we infer to
int, aslet a = 0would otherwise infer touint<1>, which would mean that the variable can only contain0 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
Opbe an operator.x Op= yis allowed iffx = x Op yis 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
lengthproperty to auintwould 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 case
let length: number = arr.length;).Questions
number? Thus,let x: intvs.let x: number.intint<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.undefinedandnullbe assigned to an integer type? The conversion functions convert them to 0. Allowingundefinedwould mean thatint + intcould beNaN(if one operand isundefined), whileNaNis not assignable to anint. I'd say thatundefinedandnullshouldn'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.