7. Conversions and Reflection
7.1. Autoboxing and Coercing
Coercing is the ability to implicitly cast one (primitive) type to another.
Autoboxing is a special kind of coercing in that is the ability to automatically convert a primitive value type, such as string
, number
, or boolean
,
to its corresponding Object type version String
, Number
, Boolean
.
The capital letters in the latter are an essential distinction.
Conversion between primitives and object-representations of a datatype are not automatic in N4JS. Only in the cases of object-method invocations on a primitive type
(for string
to call "abc".length
, for example) automatic conversion is applied.
Note that N4JS specific primitive types pathselector
and i18nkey
are handled similarly to string
.
7.1.1. Coercing
In [ECMA11a], coercing is defined by means of the abstract specification method ToPrimitive
[ECMA11a(p.S9.1)], also see [ECMA11a(p.S9.10)]).
Other conversions, such as ToNumber
, are not directly supported but reflected in the typing rules of expressions.
We express absence of automatic coercion here by means of subtype relations:
and for the N4JS specific types:
If a conversion between primitive and object type is desired, we require the user of N4JS to actively convert the values. The reason for that is the notably different behavior of object- and primitive-variants of a type in expression evaluation:
var bool: boolean = false;
var Bool: Boolean = new Boolean( false );
console.log( bool ? "true" : "false"); // prints "false"
console.log( Bool ? "true" : "false"); // prints "true"!
Conversion between a primitive type to its object-variant is achieved by the new
operator.
The valueOf()
method converts the object-variant back to a primitive value.
// objects from literals:
var bo: Boolean = new Boolean( true ); // typeof bo: object
var no: Number = new Number( 42 ); // typeof no: object
var so: String = new String( "foo" ); // typeof so: object
// to primitive
var b: boolean = bo.valueOf(); // typeof b: boolean -- true
var n: number = no.valueOf(); // typeof n: number -- 42
var s: string = so.valueOf(); // typeof s: string -- "foo"
// to object-type
bo = new Boolean( b );
no = new Number( n );
so = new String( s );
Conversion of variables of type Object
or from one primitive type to another is expressed in terms of typing rules for expressions.
That is, it is not possible to convert any Object
to a primitive in general, but it is possible to do so in the context of certain expressions such as additive operator.
The applied conversions are described in Auto-Conversion of Class Instances
7.1.2. Autoboxing of Primitives
In [ECMA11a], autoboxing is defined by ToObject
[ECMA11a(p.S9.9)].
Autoboxing is not directly supported in N4JS. Instead, primitive types virtually have the same members as their corresponding object types.
It is then possible to use the Autoboxing feature when calling a member.
In general, Autoboxing is only supported for accessing built-in read-only (immutable) properties.
For example, "some string value".split(" ");
is supported but "some string value".foo=1;
will be rejected as String does not allow properties to be added (cf. String
vs. String+
, see [_dynamic]).
Autoboxing often leads to problems, in particular in combination with dynamic types – this is why it is not directly supported in N4JS.
var s: String+ = "Hello"; (1)
s.prop = 1;
console.log(s.prop); (2)
1 | will produce an error to prevent the following scenario: |
2 | prints "undefined" |
7.1.3. Autoboxing of Function Expressions and Declarations
Function expressions and declarations always define an object of type Function
, thus coercing or Autoboxing is not required in case of functions:
It is always possible to use a function expression where a Function
is required, and to use an object of type Function
where a function expression is expected.
This is only possible if the function signatures are subtype-compatible, see Function Type for details.
Still, it is always possible to call members of Function
, e.g., function(){}.length()
.
7.2. Auto-Conversion of Objects
7.2.1. Auto-Conversion of Class Instances
All classes defined in N4JS modules implicitly subclass N4Object
, which is a plain JavaScript Object type.
The same auto-conversion rules defined for JavaScript Object
therefore apply to N4Object
instances as well.
The basic conversion uses the abstract JavaScript function ToPrimitive
[ECMA11a(p.S9.1)], which relays on the specification method Object
[ECMA11a(p.S8.12.8)].
DefaultValue
calls valueOf
or toString
methods if they are defined by the class (in the methods
-builder).
Note that according to the [ECMA11a], in most cases, objects are first converted into primitives.
That is, in most cases, no extra hint is passed to DefaultValue
. Thus valueOf
usually takes precedence over toString as demonstrated in the following example:
Assume some classes and corresponding instances defined as follows:
class A {}
class B{
@Override public toString(): string { return "MyB"}
}
class C{
@Override public valueOf(): any { return 10}
}
class D{
@Override public toString(): string { return "MyD"}
@Override public valueOf(): any { return 20}
}
var a = new A(), b = new B(), c = new C(), d = new D();
Instances of these classes will be converted as demonstrated as follows:
console.log(a+""); // [object Object]
console.log(a+1); // [object Object]1
console.log(""+b+""); // MyB
console.log(1+b+1); // 1MyB1
console.log(c+""); // 10
console.log(c+1); // 11
console.log(d+""); // 20
console.log(d+1); // 21
7.2.1.1. Auto-Conversion of Interface Instances
Instances of interfaces actually are instances of classes at runtime. The auto-conversion rules described in Auto-Conversion of Class Instances are applied to instances declared as instances of interfaces as well.
7.2.2. Auto-Conversion of Enum Literals
Enumeration values are objects and thus follow the behavior for ECMAScript Object
and Function
.
They have a custom method which returns the name of the enumeration value.
7.3. Type Cast and Type Check
7.3.1. Type Cast
(IDEBUG-56): Casting to TypeVars
Type casts are expressed with the cast expression (as
), see Cast (As) Expression for details.
We first define helper rules for the type cast constraints as follows:
Req. IDE-89: Cast Validation At Compile Time (ver. 1)
Given a type cast expression e
in which
and and target type T
, the
following constraints must hold:
-
T
must be a classifier, enum, primitive, function type expression, classifier type, type variable, union or intersection type:
-
if
S
is a subtype ofT
, the cast is unnecessary and a warning will be generated. -
if
S
andT
are classes, enums or primitive types, thenT
must be a subtype ofS
. This is also true ifT
is an interface and the type ofS
cannot have subtypes, or vice versa.
-
if
S
is a class, enum or primitive type andT
is a type-variable, then for each given boundary ofT
of type class, enum or primitiveS
must be a member of the type hierarchy: [47]
-
if
S
is a union or intersection type, then the type cast is valid if it is valid for at least one element ofS
. -
if
S
andT
are generics, and if , a cast is possible if type arguments are sub- or supertypes of each other: [48]
-
If
T
is a union type, then the type cast is valid if it is valid for at least one element ofT
. -
If
T
is an intersection type, then the type cast is valid if it is valid for all elements ofT
.
any is a supertype of all other types, thus it is always possible to cast a variable of type any to other (non-composed) types.
|
7.3.2. Type Check
There are basically two ways of testing the type of a variable: typeof
and instanceof
.
N4JS supports type comparison via the ECMAScript instanceof
operator.
The operator instanceof
retains its standard ECMAScript behavior (e.g. checking whether a value is an instance of a constructor function), but has additional functionality when used with N4JS types.
When used with an N4JS class, instanceof
also supports checking against an interface. For N4JS enumeration values, it can be used to check whether the value is part of a specific enumeration.
typeof
only returns a string with the name of the ECMAScript type, which is Object
for all class instances.
N4JS specific string
types, that is pathSelector
and i18nkey
cannot be tested during runtime.
These types, therefore, must not be used in instanceof
expressions.
The same is true for string-based enums and arrays which cannot be tested during runtime, thus string-based enum and array types are not permitted on the right-hand side of instancesof
constructs.
For all types for which the evaluation result of instanceof
could be computed at compile time, the check is unnecessary and thus it is refused by the compiler.
Using structural types on the right-hand side of instancesof
constructs is also not permitted.
In order to avoid errors at runtime, the instanceof
operator defines appropriate constraints, see Relational Expression for details.
Given the following classes and variable:
interface I{}
class S{}
class Sub extends S implements I{}
var x = new Sub();
typeof x
will simply return object
. The following table shows the difference between plain JavaScript instanceof
and N4JS’s instanceof
:
Check | JavaScript | N4JS |
---|---|---|
|
|
|
|
|
|
|
|
|
7.4. Reflection meta-information
All N4JS classes, interfaces and enumerations provide meta-information
that is used by the runtime and standard library.
All classifiers (including enums) provide meta-information by means of a static getter n4type
.
Since it is static getter, it is actually an instance getter of the
constructor (or classifier) of a type, which is the only way to retrieve
that information in case of interfaces. For enums, this can be retrieved
from instances as well.
This getter is of type N4Class
which is a built-in type just like N4Object
. It contains the following members:
fqn
-
The of the type.
n4superType
-
The
N4Class
of the supertype, may be null if supertype is a not anN4Class
. allImplementedInterfaces
-
List of The of implemented interfaces (transitively but without interfaces implemented by supertype)
get isClass
-
True if the type is an N4Class.
get isInterface
-
True if the type is an N4Interface.
7.4.1. Reflection for Classes
The meta-information for classes is available by means of N4Object
’s static
getter n4type
. Since it is static getter, it is actually an instance getter of the constructor of a type.
In addition, the static method of
in N4Type
is available to retrieve the meta-information for a given instance or
constructor. This also allows to retrieve meta-information directly for an instance of some class C
without having
the constructor of C
available, for example because the constructor is not accessible.
N4class
This example demonstrates how these reflective features are accessed:
class A {}
class B extends A {}
var b = new B();
console.log(B.n4type.fqn);
console.log(b.constructor.n4type.fqn);
console.log(b.constructor.n4type.n4superType.fqn);
console.log(N4Type.of(b));
console.log(N4Type.of(B.n4type).fqn);
Assuming this code is defined in file A
, this will output
A.B
A.B
A.A
A.B
N4BuiltInClasses.N4Class
The built-in types N4Object
and N4Class
are also accessible.
They are not defined in a module, thus their FQN returns only their simple name.
console.log('N4Object.n4class.fqn: ' + N4Object.n4class.fqn)
console.log('N4Class.n4class.fqn: ' + N4Class.n4class.fqn)
class A {}
console.log('A.n4class.fqn: ' + A.n4class.fqn)
console.log('A.n4class.n4superType.fqn: ' + A.n4class.n4superType.fqn)
Assuming this code is defined in file A
, this will output
N4Object.n4class.fqn: N4Object
N4Class.n4class.fqn: N4Class
A.n4class.fqn: A.A
A.n4class.n4superType.fqn: N4Object
Note that classes extending Object
do not provide the static n4class
getter, hat is
class B extends Object {}
console.log('B.n4class.fqn: ' + B.n4class.fqn)
would issue an error as cannot be resolved.
The type has a method to retrieve the meta-information from instances (i.e. or enumeration literals using ) without using the constructor.
class C { }
interface I {} class IImpl implements I {}
enum E { L }
var c: C = new C();
var i: I = new IImpl();
var e: E = E.L;
console.log(C.n4type.fqn);
console.log(N4Class.of(c).fqn);
console.log(I.n4type.fqn);
console.log(N4Class.of(i).fqn);
console.log(E.n4type.fqn);
console.log(N4EnumType.of(e).fqn);
7.4.2. Reflection for Interfaces
The meta-information of an interface X
is available via getter n4class
defined in the type{X}
.
This field is of type N4Class
as well.
Since an interface cannot have a super classs, the property n4superTypes
will always be empty.
Calling isInterface
respectively on the returned N4Class
instance will return true.
7.4.3. Reflection for Enumerations
var n: number; var b: boolean; var s: string;
The meta-information for enumerations is available by means of the getter n4class
, either statically by using the enumeration type or (in terms of an instance getter) via a literal.
Calling isEnum
on the returned N4Class
instance will return true.
7.5. Conversion of primitive types
Conversion between primitives is given as follows:
var n: number; var b: boolean; var s: string;
From | To | Conversion | Example |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Remarks:
-
ECMAScript doesn’t define explicit conversion from string content. Implicit handling states all strings with .
N4Primitives.parseBoolean(x)
yields true forx.trim().toLowerCase().equals("true")
-
The call to
Boolean(..)
for the arguments0
,-0
,null
,false
,NaN
,undefined
and""
evaluate tofalse
. All other values evaluate totrue
. -
Number
has several methods for converting a value to string [ECMA11a(p.S15.7.4)]:toExponential()
, toFixed()
,toPrecision()
. -
ECMAScript doesn’t define explicit conversion from boolean to number. Implicit handling states true 1 and false 0, which
N4Primitives.toNumber()
yields.