Type Conversions
Basic Storm attempts to limit automatic typecasts to avoid situations where information is lost due to an unintended implicit type conversion. This is, however, overly strict in many situations, so Basic Storm allows some level of implicit type conversions that are considered safe.
The remainder of this section describes the different type conversions available, both explicit and implicit ones.
Explicit Type Conversions
There is technically no standard way to perform explicit type conversions in Basic Storm. Instead,
Basic Storm relies on the standard library. All primitive types contain member functions for
conversion between the relevant member types. For example, one can convert from an integer
(core:Int
) to an unsigned integer (core:Nat
) by calling the .nat
member available in the Int
type (and most other integer types). Some examples are as follows:
10i.nat; // Convert Int => Nat 10n.int; // Convert Nat => Int 10.0.int; // Convert Float => Int
Most types also have a toS
(short for toString
) that converts it to a string. Value types may
instead have an output operator <<
. If that is the case, then a toS
function is automatically
generated for them in the system.
Other user-defined types typically have constructors for converting between types. As such, for two types might be possible to explicitly call the constructor to convert between the types. For example:
T original; U copy(original); // Constructor call at creation time U(original); // Constructor call as a part of an expression.
Implicit Type Conversions
As previously mentioned, some type conversions are performed implicitly. Basic Storm allows the following conversions implicitly:
- Converting from a derived type to a super type.
-
Calling a constructor of the target type that is marked as a cast constructor (keyword
cast
in Basic Storm). Note that this is only checked in one level. Namely, if an implicit conversion fromT
toU
is required, Basic Storm will only attempt to check ifU(t)
is possible. As such, the implicit conversion will not be allowed even if there exists a typeV
that could be used asU(V(t))
. -
Converting from
T
toMaybe<T>
as required. This is actually implemented byMaybe<T>
having cast constructors. However, there are some special cases that Basic Storm takes explicit care of in this regard (namely in if-statements). -
Integer literals can be automatically converted to another type, assuming the value
of the literal is not too large for the target type. For example, the literal
0xFF
can be automatically casted fromcore:Word
tocore:Byte
andcore:Nat
since it is small enough. However,0x100000000
is too large to fit in bothcore:Byte
andcore:Nat
, so those conversions are not allowed. - Any other conversions implemented by custom AST nodes. For example, the array literals and the function pointers utilize this mechanism to automatically infer types in some cases. The scope of these are, however, generally limited and not surprising.
Weak Casts
Certain type conversions do not always succeed, but may fail at times. Examples of such casts are
casts from a super type to a derived type, and casts from Maybe<T>
into T
. The cast from a super
type to a derived type only succeeds if the dynamic type of the variable matches the expected type,
and the cast from Maybe<T>
to T
only succeeds if the Maybe<T>
did not contain null
.
To accommodate this type of casts in a safe way, Basic Storm introduces the concept of a weak cast. These casts need to be embedded in a conditional statement so that the compiler can ensure that it is only possible to access the result of the cast if it succeeded.
The remainder of the documentation on weak casts will use if
statements to illustrate the
behavior. However, it is possible to use weak casts in unless
statements and while
loops as
well.
A weak cast has the following form in general:
if (<variable> = <weak cast>) { // <variable> is visible here } else { // <variable> is not visible here } // <variable> is not visible here either
The <weak cast>
itself can take different forms depending on the cast that is attempted.
Currently, the following two forms are implemented. More can be added by extending the rule
lang:bs:SWeakCast
:
-
<exression>
: Cast from aMaybe<T>
intoT
. -
<expression> as T
: Cast from a super type to a derived type, or from aVariant
to the specified type. Also handles cases where<expression>
is aMaybe
-type.
For example, they can be used as follows:
void myFunction(Base? x) { if (d = x as Derived) { // 'd' is now of type Derived. } }
As a special case, whenever <expression>
only refers to a local- or member variable, the
<variable>
part in the if
statement may be omitted. In that case, Basic Storm creates a local
variable that shadows the original variable instead. As such, the following two are equivalent:
void myFnA(Base? x) { if (x = x as Derived) { // 'x' is now of type Dervied. } } void myFnB(Base? x) { if (x as Derived) { // 'x' is now of type Dervied. } }
Of course, this works with casts from Maybe<T>
into T
as well. This makes null-checks look like
in C++:
void myFn(Base? x) { if (x) { // 'x' is now of type Base (i.e. not Maybe<Base> as the parameter is). } }
The <variable>
part may also be omitted for other expressions. However, in that case there is no
way to access the result of the successful cast.
For completeness, we provide the following examples of using weak casts with unless
and while
statements:
void f(Int? to) { unless (to) return -1; to + 20; }
Note that the branch following the unless
statement needs to return or throw an exception to
ensure type-safety of the to + 20
part of the code. The code above is equivalent to the following:
void foo(Int? to) { if (to) { to + 20; } else { -1; } }
Finally, the ability to use weak casts in while
loops makes it convenient to use the iterators
from WeakSet
among others:
void iterate(WeakSet<Int> set) { var iter = set.iter(); while (elem = iter.next()) { // Do something } }