In general, the semantics of the Scala.js language are the same as Scala onthe 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 threeexceptions.
Floats can behave as Doubles by default
Scala.js underspecifies the behavior of Float
s by default.Any Float
value can be stored as a Double
instead, and any operation onFloat
s can be computed with double precision.The choice of whether or not to behave as such, when and where, is left to theimplementation.
If exact single precision operations are important to your application, you canenable strict-floats semantics in Scala.js, with the following sbt setting:
scalaJSLinkerConfig ~= { _.withSemantics(_.withStrictFloats(true)) }
Note that this can have a major impact on performance of your application onJS interpreters that do not supportthe Math.fround
function.
toString of Float, Double and Unit
x.toString()
returns slightly different results for floating point numbersand ()
(Unit
).
().toString // "undefined", instead of "()"
1.0.toString // "1", instead of "1.0"
1.4f.toString // "1.399999976158142" instead of "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 thetype they were created with. The following are examples:
- 1 matches
Byte
,Short
,Int
,Float
,Double
- 128 (
> Byte.MaxValue
) matchesShort
,Int
,Float
,Double
- 32768 (
> Short.MaxValue
) matchesInt
,Float
,Double
- 2147483647 matches
Int
,Double
if strict-floats are enabled(because that number cannot be represented in a strict 32-bitFloat
),otherwiseInt
,Float
andDouble
- 2147483648 (
> Int.MaxValue
) matchesFloat
,Double
- 1.5 matches
Float
,Double
- 1.4 matches
Double
only if strict-floats are enabled,otherwiseFloat
andDouble
(unlike 1.5, the value 1.4 cannot be represented in a strict 32-bitFloat
) NaN
,Infinity
,-Infinity
and-0.0
matchFloat
,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 somebugs are reported as exceptions.Currently known exhaustive list of exceptions are:
NullPointerException
ArrayIndexOutOfBoundsException
andStringIndexOutOfBoundsException
ClassCastException
ArithmeticException
(such as integer division by 0)StackOverflowError
and otherVirtualMachineError
s
Because Scala.js does not receive VM support to detect such erroneousconditions, checking them is typically too expensive.
Therefore, all of these are consideredundefined behavior.
Some of these, however, can be configured to be compliant with the JVMspecification using sbt settings.Currently, only ClassCastException
s (thrown by invalid asInstanceOf
calls)are configurable, but the list will probably expand in future versions.
Every configurable undefined behavior has 3 possible modes:
Compliant
: behaves as specified on a JVMUnchecked
: completely unchecked and undefinedFatal
: checked, but throwsUndefinedBehaviorError
sinstead of the specified exception.
By default, undefined behaviors are in Fatal
mode for fastOptJS
and inUnchecked
mode for fullOptJS
.This is so that bugs can be detected more easily during development, withpredictable exceptions and stack traces.In production code (fullOptJS
), the checks are removed for maximumefficiency.
UndefinedBehaviorError
s are fatal in the sense that they are not matched bycase NonFatal(e)
handlers.This makes sure that they always crash your program as early as possible, sothat you can detect and fix the bug.It is never OK to catch an UndefinedBehaviorError
(other than in a testingframework), 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 theJVM semantics, you can do so with an sbt setting.For example, this setting enables compliant asInstanceOf
s:
scalaJSLinkerConfig ~= { _.withSemantics(_.withAsInstanceOfs(
org.scalajs.core.tools.sem.CheckedBehavior.Compliant)) }
Note that this will have (potentially major) performance impacts.
JavaScript interoperability
The JavaScript interoperability feature is, in itself, a big semanticdifference. However, its details are discussed in adedicated page.
Reflection
Java reflection and, a fortiori, Scala reflection, are not supported. There islimited support for java.lang.Class
, e.g., obj.getClass.getName
will workfor any Scala.js object (not for objects that come from JavaScript interop).
Regular expressions
JavaScript regular expressionsare slightly different fromJava regular expressions.The support for regular expressions in Scala.js is implemented on top ofJavaScript regexes.
This sometimes has an impact on functions in the Scala library thatuse regular expressions themselves. A list of known functions that areaffected is given here:
StringLike.split(x: Array[Char])
(see issue #105)
Symbols
scala.Symbol
is supported, but is a potential source of memory leaksin applications that make heavy use of symbols. The main reason is thatJavaScript does not support weak references, causing all symbols createdby Scala.js to remain in memory throughout the lifetime of the application.
Enumerations
The methods Value()
and Value(i: Int)
on scala.Enumeration
usereflection to retrieve a string representation of the member name andare therefore – in principle – unsupported. However, sinceEnumerations are an integral part of the Scala library, Scala.js addslimited support for these two methods:
- Calls to either of these two methods of the forms:
val <ident> = Value
val <ident> = Value(<num>)
are statically rewritten to (a slightly more complicated version of):
val <ident> = Value("<ident>")
val <ident> = Value(<num>, "<ident>")
Note that this also includes calls like
val A, B, C, D = Value
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 anexplicit name as parameter, will issue a warning.Note that the name rewriting honors thenextName
iterator. Therefore, the full rewrite is:
val <ident> = Value(
if (nextName != null && nextName.hasNext)
nextName.next()
else
"<ident>"
)
We believe that this covers most use cases ofscala.Enumeration
. Please let us know if another (generalized)rewrite would make your life easier.