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:
-
Tmust be a classifier, enum, primitive, function type expression, classifier type, type variable, union or intersection type:
-
if
Sis a subtype ofT, the cast is unnecessary and a warning will be generated. -
if
SandTare classes, enums or primitive types, thenTmust be a subtype ofS. This is also true ifTis an interface and the type ofScannot have subtypes, or vice versa.
-
if
Sis a class, enum or primitive type andTis a type-variable, then for each given boundary ofTof type class, enum or primitiveSmust be a member of the type hierarchy: [47]
-
if
Sis a union or intersection type, then the type cast is valid if it is valid for at least one element ofS. -
if
SandTare generics, and if , a cast is possible if type arguments are sub- or supertypes of each other: [48]
-
If
Tis a union type, then the type cast is valid if it is valid for at least one element ofT. -
If
Tis 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
N4Classof 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.
N4classThis 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,undefinedand""evaluate tofalse. All other values evaluate totrue. -
Numberhas 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.
