Conversions¶
A conversion converts an expression of one type to another type, possibly changing its value. In certain cases noted below the source expression can be a type expression. We refer to these two types the source and target types. Conversions can be either implicit (Implicit Conversions) or explicit (Explicit Conversions).
Implicit Conversions¶
An implicit conversion is a conversion that occurs implicitly, that is, not due to an explicit specification in the program. Implicit conversions occur at the locations in the program listed below. Each location determines the target type. The source and target types of an implicit conversion must be allowed. They determine whether and how the expression’s value changes.
An implicit conversion occurs at each of the following program locations:
- In an assignment, the expression on the right-hand side of the assignment is converted to the type of the variable or another lvalue on the left-hand side of the assignment.
- In a variable or field declaration, the initializing expression is
converted to the type of the variable or field. The initializing
expression is the r.h.s. of the
=
in the declaration, if present, or in the field initialization statement in an initializer. - The actual argument of a function call or an operator is converted to
the type of the corresponding formal argument, if the formal’s intent
is
param
,in
,const in
, or an abstract intent (Abstract Intents) with the semantics ofin
orconst in
. - The actual type argument of a function call or an operator is
converted to the corresponding formal argument of the
type
intent or thethis
formal of a type method. See Implicit Type Argument Conversions. - If the formal argument’s intent is
out
, the formal argument is converted to the type of the corresponding actual argument upon function return. - The return or yield expression within a function without a
ref
return intent is converted to the return type of that function. - The condition of a conditional expression, conditional statement, while-do or do-while loop statement is converted to the boolean type. See Implicit Statement Bool Conversions.
Implicit conversions are not applied for actual arguments passed to
ref
or const ref
formal arguments.
Implicit conversions are allowed between the following source and target types, as defined in the referenced subsections:
- numeric and boolean types (Implicit Numeric and Bool Conversions),
- numeric types in the special case when the expression’s value is a compile-time constant (Implicit Compile-Time Constant Conversions), and
- class types (Implicit Class Conversions),
- class and generic types in certain cases (Implicit Type Argument Conversions)
- from an integral or class type to
bool
in certain cases (Implicit Statement Bool Conversions). - generic target types (Implicit Conversions To Generic Types)
In addition, an implicit conversion from a type to the same type is allowed for any type. Such conversion does not change the value of the expression.
Implicit conversion is not transitive. That is, if an implicit
conversion is allowed from type T1
to T2
and from T2
to
T3
, that by itself does not allow an implicit conversion from T1
to T3
.
Implicit Numeric and Bool Conversions¶
Implicit conversions among numeric types are allowed when all values
representable in the source type can also be represented in the target
type, retaining their full precision. In addition, implicit conversions
from types int(64)
and uint(64)
to types real(64)
and
complex(128)
are allowed, even though they may result in a loss of
precision.
Rationale.
We allow these additional conversions because they are an important convenience for application programmers. Therefore we are willing to lose precision in these cases. The largest real and complex types are chosen to retain precision as often as as possible.
Any boolean type can be implicitly converted to any other boolean type,
retaining the boolean value. Any boolean type can be implicitly
converted to any integral type by representing false
as 0 and
true
as 1, except (if applicable) a boolean cannot be converted to
int(1)
.
Rationale.
We disallow implicit conversion of a boolean to a real, imaginary, or complex type because of the following. We expect that the cases where such a conversion is needed will more likely be unintended by the programmer. Marking those cases as errors will draw the programmer’s attention. If such a conversion is actually desired, a cast Explicit Conversions can be inserted.
Legal implicit conversions with numeric and boolean types may thus be tabulated as follows:
Source Type | bool(\(t\)) | uint(\(t\)) | int(\(t\)) | real(\(t\)) | imag(\(t\)) | complex(\(t\)) |
bool(\(s\)) | all \(s,t\) | all \(s,t\) | all \(s\); \(2 \le t\) | |||
uint(\(s\)) | \(s \le t\) | \(s < t\) | \(s \le mant(t)\) | \(s \le mant(t/2)\) | ||
uint(64) | real(64) | complex(128) | ||||
int(\(s\)) | \(s \le t\) | \(s \le mant(t)+1\) | \(s \le mant(t/2)+1\) | |||
int(64) | real(64) | complex(128) | ||||
real(\(s\)) | \(s \le t\) | \(s \le t/2\) | ||||
imag(\(s\)) | \(s \le t\) | \(s \le t/2\) | ||||
complex(\(s\)) | \(s \le t\) |
Here, \(mant(i)\) is the number of bits in the (unsigned) mantissa
of the \(i\)-bit floating-point type. [1] Conversions for the
default integral and real types (uint
, complex
, etc.) are the
same as for their explicitly-sized counterparts.
Implicit Compile-Time Constant Conversions¶
A parameter of numeric type can be implicitly converted to any other
numeric type if the value of the parameter can be represented exactly by
the target type. This rule does not allow conversions from real
to
imag
, or from complex
to a non-complex type. It does allow
conversions from real
or imag
to complex
.
Implicit Class Conversions¶
An expression of class type can be implicitly converted to the borrow
type; to a nilable type; or to a parent class type. The value nil
can be implicitly converted to any nilable class type.
First, class types can be converted to the corresponding borrowed
type. For example, owned C
can be implicitly converted to
borrowed C
, and shared C?
can be implicitly converted to
borrowed C?
. This coercion is equivalent to calling the
.borrow()
method. See Class Lifetime and Borrows.
For example:
Example (implicit-conversion-to-borrow.chpl).
class C { } var c:owned C = new owned C(); proc f(arg: borrowed C) { } f(c); // equivalent to f(c.borrow())
Second, an expression of non-nilable class type can be implicitly converted to the nilable class type. Continuing the above example:
Example (implicit-conversion-to-nilable.chpl).
var b:borrowed C = c.borrow(); proc g(arg: borrowed C?) { } g(b); // equivalent to g(b:borrowed C?)
Third, an implicit conversion from class type D
to another class
type C
is allowed when D
is a subclass of C
.
Any combination of these three conversions is allowed.
Implicit Type Argument Conversions¶
An implicit type argument conversion applies only when a type actual is
passed to a formal with the type
intent. This includes the this
formal of a type method. In this case, a subset of Implicit Class
Conversions (Implicit Class Conversions) applies, in
addition to Implicit Conversions To Generic Types
(Implicit Conversions To Generic Types).
Future.
The details are forthcoming.
Implicit Statement Bool Conversions¶
In the condition of an if-statement, while-loop, and do-while-loop, the
following implicit conversions to bool
are supported:
- An expression of integral type is taken to be false if it is zero and is true otherwise.
- An expression of a class type is taken to be false if it is nil and is true otherwise.
Implicit Conversions To Generic Types¶
When the target type T
is generic (Generic Types), an
implicit conversion is allowed when there is an instantiation of this
type such that an implicit conversion is allowed between the source type
and that instantiation by another rule in this section.
That instantiation is taken to be the instantiated type of the variable,
field, formal argument, or the return type whose declared type is the
generic type T
.
The conversions in this subsection apply when the source is either an expression or a type expression.
Explicit Conversions¶
Explicit conversions require a cast in the code. Casts are defined in Casts. Explicit conversions are supported between more types than implicit conversions, but not between all types.
The explicit conversions are a superset of the implicit conversions. In addition to the following definitions, an explicit conversion from a type to the same type is allowed for any type. Such conversion does not change the value of the expression.
Explicit Numeric Conversions¶
Explicit conversions are allowed from any numeric type or boolean to bytes or string, and vice-versa.
When a bool
is converted to a bool
, int
or uint
of equal
or larger size, its value is zero-extended to fit the new
representation. When a bool
is converted to a smaller bool
,
int
or uint
, its most significant bits are truncated (as
appropriate) to fit the new representation.
When a int
, uint
, or real
is converted to a bool
, the
result is false
if the number was equal to 0 and true
otherwise.
When an int
is converted to a larger int
or uint
, its value
is sign-extended to fit the new representation. When a uint
is
converted to a larger int
or uint
, its value is zero-extended.
When an int
or uint
is converted to an int
or uint
of
the same size, its binary representation is unchanged. When an int
or uint
is converted to a smaller int
or uint
, its value is
truncated to fit the new representation.
Future.
There are several kinds of integer conversion which can result in a loss of precision. Currently, the conversions are performed as specified, and no error is reported. In the future, we intend to improve type checking, so the user can be informed of potential precision loss at compile time, and actual precision loss at run time. Such cases include: When an
int
is converted to auint
and the original value is negative; When auint
is converted to anint
and the sign bit of the result is true; When anint
is converted to a smallerint
oruint
and any of the truncated bits differs from the original sign bit; When auint
is converted to a smallerint
oruint
and any of the truncated bits is true;
Rationale.
For integer conversions, the default behavior of a program should be to produce a run-time error if there is a loss of precision. Thus, cast expressions not only give rise to a value conversion at run time, but amount to an assertion that the required precision is preserved. Explicit conversion procedures would be available in the run-time library so that one can perform explicit conversions that result in a loss of precision but do not generate a run-time diagnostic.
When converting from a real
type to a larger real
type, the
represented value is preserved. When converting from a real
type to
a smaller real
type, the closest representation in the target type
is chosen. [2]
When converting to a real
type from an integer type, integer types
smaller than int
are first converted to int
. Then, the closest
representation of the converted value in the target type is chosen. The
exact behavior of this conversion is implementation-defined.
When converting from real(k)
to complex(2k)
, the original
value is copied into the real part of the result, and the imaginary part
of the result is set to zero. When converting from a real(k)
to a
complex(j)
such that j > 2k
, the conversion is
performed as if the original value is first converted to
real(j/2)
and then to j
.
The rules for converting from imag
to complex
are the same as
for converting from real, except that the imaginary part of the result
is set using the input value, and the real part of the result is set to
zero.
Explicit Tuple to Complex Conversion¶
A two-tuple of numerical values may be converted to a complex
value.
If the destination type is complex(128)
, each member of the
two-tuple must be convertible to real(64)
. If the destination type
is complex(64)
, each member of the two-tuple must be convertible to
real(32)
. The first member of the tuple becomes the real part of the
resulting complex value; the second member of the tuple becomes the
imaginary part of the resulting complex value.
Explicit Enumeration Conversions¶
Explicit conversions are allowed from any enumerated type to bytes
or string
and vice-versa, including param
conversions. For
enumerated types that are either concrete or semi-concrete (see
Enumerated Types), conversions are supported from the enum to
any numeric or boolean type, including param
conversions.
Explicit conversions are also supported from integer values back to
concrete or semi-concrete enumerated types.
When converting from an enum to a bytes
or string
, the value
becomes the name of the enumerator.
When converting from a bytes
or string
to an enum, the result
is the constant whose name matches the source value. If no matching
value exists, an IllegalArgumentError
is thrown.
For a semi-concrete enumerated type, if a numeric conversion is
attempted for a constant with no underlying integer value, it will
generate a compile-time error for a param
conversion or throw an
IllegalArgumentError
otherwise.
When converting from an enum to an integer type, the value is first converted to the enum’s underlying integer type and then to the target type, following the rules above for converting between integers.
When converting from an enum to a real, imaginary, or complex type, the value is first converted to the enum’s underlying integer type and then to the target type.
When converting from an enum to a boolean type, the value is first
converted to the enum’s underlying integer type. If the result is
zero, the value of the bool
is false
; otherwise, it is
true
.
When converting from an integer value to an enum, the value is
converted to the enum’s underlying integer type and then converted to
the matching symbol. If no symbol has the given integer value, an
IllegalArgumentError
is thrown.
Explicit Class Conversions¶
An expression of static class type C
can be explicitly converted to
a class type D
provided that C
is derived from D
or D
is
derived from C
.
When at run time the source expression refers to an instance of D
or
it subclass, its value is not changed. Otherwise, the cast fails and the
result depends on whether or not the destination type is nilable. If the
cast fails and the destination type is not nilable, the cast expression
will throw a classCastError
. If the cast fails and the destination
type is nilable, as with D?
, then the result will be nil
.
An expression of class type can also be converted to a different
nilability with a cast. For conversions from a nilable class type to a
non-nilable class type, the cast will throw a NilClassError
if the
value was actually nil
.
In some cases a new variant of a class type needs to be computed that
has different nilability or memory management strategy. Supposing that
T
represents a class type, then these casts may compute a new type:
T:owned
- new management isowned
, nilability fromT
T:shared
- new managementshared
, nilability fromT
T:borrowed
- new managementborrowed
, nilability fromT
T:unmanaged
- new managementunmanaged
, nilability fromT
T:class
- non-nilable type with specific concrete or generic management fromT
T:class?
- nilable type with specific concrete or generic management fromT
T:owned class
- non-nilable type withowned
managementT:owned class?
- nilable type withowned
managementT:shared class
- non-nilable type withshared
managementT:shared class?
- nilable type withshared
managementT:borrowed class
- non-nilable type withborrowed
managementT:borrowed class?
- nilable type withborrowed
managementT:unmanaged class
- non-nilable type withunmanaged
managementT:unmanaged class?
- nilable type withunmanaged
management
The conversions in this subsection apply when the source is either an expression or a type expression.
Explicit Range Conversions¶
An expression of stridable range type can be explicitly converted to an unstridable range type, changing the stride to 1 in the process.
Explicit Domain Conversions¶
An expression of stridable domain type can be explicitly converted to an unstridable domain type, changing all strides to 1 in the process.
Explicit String to Bytes Conversions¶
An expression of string
type can be explicitly converted to a
bytes
. However, the reverse is not possible as a bytes
can
contain arbitrary bytes. Instead, bytes.decode()
method should be
used to produce a string
from a bytes
.
Explicit Type to String Conversions¶
A type expression can be explicitly converted to a string
. The
resultant string
is the name of the type.
Example (explicit-type-to-string.chpl).
For example:
var x: real(64) = 10.0; writeln(x.type:string);This program will print out the string
"real(64)"
.
[1] | For the IEEE 754 format, \(mant(32)=24\) and \(mant(64)=53\). |
[2] | When converting to a smaller real type, a loss of precision is expected. Therefore, there is no reason to produce a run-time diagnostic. |