In general, the semantics of the Scala.js language are the same as Scala on
the JVM.
However, a few differences exist, which we mention here.
Primitive data types
All primitive data types work exactly as on the JVM, with the following three
exceptions.
Floats can behave as Doubles by default
Scala.js underspecifies the behavior of Floats by default.
Any Float value can be stored as a Double instead, and any operation on
Floats can be computed with double precision.
The choice of whether or not to behave as such, when and where, is left to the
implementation.
If exact single precision operations are important to your application, you can
enable strict-floats semantics in Scala.js, with the following sbt setting:
scalaJSSemantics~={_.withStrictFloats(true)}
Note that this can have a major impact on performance of your application on
JS interpreters that do not support
the Math.fround function.
toString of Float, Double and Unit
x.toString() returns slightly different results for floating point numbers
and () (Unit).
().toString// "undefined", instead of "()"
1.0.toString// "1", instead of "1.0"
1.4f.toString//"1.399999976158142"insteadof"1.4"
In general, a trailing .0 is omitted.
Floats print in a weird way because they are printed as if they were Doubles,
which means their lack of precision shows up.
To get sensible and portable string representation of floating point numbers,
use String.format() or related methods.
Runtime type tests are based on values
Instance tests (and consequently pattern matching) on any of Byte,
Short, Int, Float, Double are based on the value and not the
type they were created with. The following are examples:
2147483647 matches Int, Double if strict-floats are enabled
(because that number cannot be represented in a strict 32-bit Float),
otherwise Int, Float and Double
2147483648 (> Int.MaxValue) matches Float, Double
1.5 matches Float, Double
1.4 matches Double only if strict-floats are enabled,
otherwise Float and Double
(unlike 1.5, the value 1.4 cannot be represented in a strict 32-bit Float)
NaN, Infinity, -Infinity and -0.0 match Float, Double
As a consequence, the following apparent subtyping relationships hold:
Byte <:< Short <:< Int <:< Double
<:< Float <:<
if strict-floats are enabled, or
Byte <:< Short <:< Int <:< Float =:= Double
otherwise.
Undefined behaviors
The JVM is a very well specified environment, which even specifies how some
bugs are reported as exceptions.
Some examples are:
NullPointerException
ArrayIndexOutOfBoundsException and StringIndexOutOfBoundsException
ClassCastException
ArithmeticException (such as integer division by 0)
StackOverflowError and other VirtualMachineErrors
Because Scala.js does not receive VM support to detect such erroneous
conditions, checking them is typically too expensive.
Some of these, however, can be configured to be compliant with the JVM
specification using sbt settings.
Currently, only ClassCastExceptions (thrown by invalid asInstanceOf calls)
are configurable, but the list will probably expand in future versions.
Every configurable undefined behavior has 3 possible modes:
By default, undefined behaviors are in Fatal mode for fastOptJS and in
Unchecked mode for fullOptJS.
This is so that bugs can be detected more easily during development, with
predictable exceptions and stack traces.
In production code (fullOptJS), the checks are removed for maximum
efficiency.
UndefinedBehaviorErrors are fatal in the sense that they are not matched by
case NonFatal(e) handlers.
This makes sure that they always crash your program as early as possible, so
that you can detect and fix the bug.
It is never OK to catch an UndefinedBehaviorError (other than in a testing
framework), since that means your program will behave differently in fullOpt
stage than in fastOpt.
If you need a particular kind of exception to be thrown in compliance with the
JVM semantics, you can do so with an sbt setting.
For example, this setting enables compliant asInstanceOfs:
Note that this will have (potentially major) performance impacts.
JavaScript interoperability
The JavaScript interoperability feature is, in itself, a big semantic
difference. However, its details are discussed in a
dedicated page.
Reflection
Java reflection and, a fortiori, Scala reflection, are not supported. There is
limited support for java.lang.Class, e.g., obj.getClass.getName will work
for any Scala.js object (not for objects that come from JavaScript interop).
This sometimes has an impact on functions in the Scala library that
use regular expressions themselves. A list of known functions that are
affected is given here:
scala.Symbol is supported, but is a potential source of memory leaks
in applications that make heavy use of symbols. The main reason is that
JavaScript does not support weak references, causing all symbols created
by Scala.js to remain in memory throughout the lifetime of the application.
Enumerations
The methods Value() and Value(i: Int) on scala.Enumeration use
reflection to retrieve a string representation of the member name and
are therefore – in principle – unsupported. However, since
Enumerations are an integral part of the Scala library, Scala.js adds
limited support for these two methods:
Calls to either of these two methods of the forms:
val<ident>=Valueval<ident>=Value(<num>)
are statically rewritten to (a slightly more complicated version of):
since they are desugared into separate val definitions.
Calls to either of these two methods which could not be rewritten,
or calls to constructors of the protected Val class without an
explicit name as parameter, will issue a warning.
Note that the name rewriting honors the nextName
iterator. Therefore, the full rewrite is: