Conversions¶
A conversion converts an expression of one type to another type, possibly producing a new value. In certain cases noted below, the source expression can be a type expression. We refer to these two types as 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, without an explicit operation within the program. Implicit conversions fall into the following categories:
implicit conversions for initialization and assignment (Implicit Conversions for Initialization and Assignment)
implicit conversions for function calls (Implicit Conversions for Function Calls)
implicit conversions for conditionals (Implicit Conversions for Conditionals)
If an implicit conversion for a function call is allowed from type T1
to
type T2
then implicit conversion for initialization and assignment is
allowed.
In addition, an implicit conversion from a type to the same type is allowed for any type. Such a 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 conversion for function calls, initialization, and assignment are allowed between the following source and target types, as defined in the referenced subsections:
boolean and numeric types 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),
class types (Implicit Class Conversions), and
when the source type is a subtype of the target type (including when the target type is generic) (Implicit Subtype Conversions)
Additionally, implicit conversions for initialization and assignment can be defined for record types, as specified in Implicit Conversions for Initialization and Assignment.
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
are permitted from int(s)
and uint(s)
values to real(t)
and
complex(2*t)
, for any widths s
and t
, even though these cases
may result in a loss of precision.
Rationale.
We allow these additional conversions because they provide an important convenience for application programmers who want to mix integral and floating point values in mathematical expressions, and for computing using values using a specific bit-width. For these benefits, the loss of precision seemed like a reasonable tradeoff, particularly given that floating point types are approximate by nature.
Signed integral types int(s)
can implicitly convert to uint(s)
where s <= t
.
Rationale.
We allow these conversions to avoid the situation that something similar to a binary operator produces surprising results when mixing
int
anduint
types. In particular, without this rule, theplus
function defined below would surprisingly produce values of a different width or a different kind:proc plus(a: int(32), b: int(32)) : int(32) { ... } proc plus(a: int(64), b: int(64)) : int(64) { ... } proc plus(a: uint(32), b: uint(32)) : uint(32) { ... } proc plus(a: uint(32), b: uint(32)) : uint(32) { ... } proc plus(a: real(64), b: real(64)) : real(64) { ... } var myInt32: int(32); var myUint32: uint(32); plus(myInt32, myUint32); // calls 'uint(32)' version, but // without int->uint implicit conversion, // would call the 'int(64)' version var myInt64: int(64); var myUint64: uint(64); plus(myInt64, myUint64); // calls 'uint(64)' version, but // without int->uint implicit conversion, // would call the 'real(64)' versionWhile implicitly converting an
int
to auint
can lead to surprising behavior, this behavior is less problematic than the surprising behavior that comes from the above scenario.
A bool
can be implicitly converted to any integral type by
representing false
as 0 and true
as 1.
Rationale.
We disallow implicit conversion of a
bool
to a real, imaginary, or complex type because we expect that such conversions are most likely to be an unintended mistake by the programmer. Marking such cases as errors will draw the programmer’s attention to the issue, and if such a conversion is actually desired, a cast can be used (see Explicit Conversions).
Legal implicit conversions with numeric and boolean types may thus be summarized as follows:
Destination Type |
|||||
Source Type |
uint(\(t\)) |
int(\(t\)) |
real(\(t\)) |
imag(\(t\)) |
complex(\(t\)) |
bool |
all \(t\) |
all \(t\) |
|||
uint(\(s\)) |
\(s \le t\) |
\(s < t\) |
all \(s,t\) |
all \(s,t\) |
|
int(\(s\)) |
\(s \le t\) |
\(s \le t\) |
all \(s,t\) |
all \(s,t\) |
|
real(\(s\)) |
\(s \le t\) |
\(s \le t/2\) |
|||
imag(\(s\)) |
\(s \le t\) |
\(s \le t/2\) |
|||
complex(\(s\)) |
\(s \le t\) |
Implicit Compile-Time Constant Conversions¶
A param
of numeric type can be implicitly converted to another numeric
type in some cases if the param
value can be represented exactly by
the target type. In particular:
param
int(s)
anduint(s)
values that are exactly representable in the target type can implicit convert toint(t)
anduint(t)
regardless of the values ofs
andt
.
param
real(s)
that is exactly representable in the target type can implicitly convert toreal(t)
or tocomplex(t)
regardless of the values ofs
andt
.
param
imag(s)
that is exactly representable in the target type can implicitly convert toimag(t)
or tocomplex(t)
regardless of the values ofs
andt
.
param
complex(s)
that is exactly representable in the target type can implicitly convert tocomplex(t)
.
As with the implicit numeric conversions, integral param
values can
implicitly convert:
to
uint
of matching or greater size or toreal
or, to
complex
of any size.
Implicit Class Conversions¶
- An expression of class type can be implicitly converted to:
to a parent class type,
to a nilable type, or
to the borrow type.
Any combination of these three conversions is allowed.
The value nil
can be implicitly converted to any nilable class type.
Conversion to a parent class type or to a nilable type is a subtype conversion and is discussed in the next section (Implicit Subtype Conversions).
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())
Implicit Subtype Conversions¶
An implicit subtype conversion is allowed when the source type is a subtype of the target type.
Given any two types T1
and T2
, the type T1
is considered to be a
subtype of a type T2
if:
T2
is a generic type (Generic Types) andT1
is an instantiation that type
T1
is a class type that inherits from the classT2
(Inheritance)
T1
is a non-nilable class type (e.g.borrowed C
) andT2
is the nilable version of the same class type (e.g.borrowed C?
) (Nilable Class Types)or a combination of the above.
The below examples use isSubtype
to demonstrate
when one type is a subtype of another.
Example (not-a-subtype.chpl)
The following code snippet demonstrates that
int(8)
is not a subtype ofint
. Note that, even though anint(8)
value can be implicitly converted toint
,int(8)
is not a subtype ofint
.isSubtype(int(8), int); // evaluates to falseExample (subtype-int8-integral.chpl)
However,
int(8)
is a subtype of the generic typeintegral
according to the first rule above (Built-in Generic Types).isSubtype(int(8), integral); // evaluates to trueExample (subtype-pass-int8-integral.chpl)
Since
int(8)
is a subtype ofintegral
, the typeint(8)
can be passed to the type argumenttype t: integral
(Legal Argument Mapping). As a result the following program will compile:proc f(type t: integral) { } f(int(8));Example (subtype-parent-class.chpl)
This example demonstrates that
ChildClass
is a subtype ofParentClass
.class ParentClass { } class ChildClass : ParentClass { } writeln(isSubtype(ChildClass, ParentClass)); // outputs true writeln(isSubtype(borrowed ChildClass, borrowed ParentClass)); // outputs true proc f(type t: ParentClass) { } f(ChildClass); // implicit subtype conversion proc g(type t: borrowed ParentClass) { } g(borrowed ChildClass); // implicit subtype conversion // The implicit subtype conversion can also apply to non-type arguments: proc h(in arg: owned ParentClass) { } h(new owned ChildClass()); // implicit subtype conversionExample (subtype-nilable.chpl).
This example shows that a non-nilable class type is a subtype of a nilable class type with the same management.
class C { } writeln(isSubtype(C, C?)); // outputs true writeln(isSubtype(owned C, owned C?)); // outputs trueExample (subtype-three.chpl).
This example demonstrates a combination of all three rules. Note that
ParentClass
indicates a generic memory management strategy (Class Types).class ParentClass { } class ChildClass : ParentClass { } writeln(isSubtype(ChildClass, ParentClass?)); // outputs true proc f(type t: ParentClass?) { } f(ChildClass); // uses implicit subtype conversion proc g(in arg: ParentClass?) { } g(new owned ChildClass()); // uses implicit subtype conversion
Implicit Conversions for Initialization and Assignment¶
An implicit conversion for initialization or assignment 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 expression on the left-hand side of the assignment.
In a variable or field declaration that is not a ref variable, the initializing expression is converted to the type of the variable or field. The initializing expression is the right-hand side of the
=
in the declaration, if present, or in the field initialization statement in an initializer.The return or yield expression within a function without a
ref
orconst ref
return intent is converted to the return type of that function.For a call to a function with a formal argument with
out
orinout
intent. The value of the formal argument is converted to the type of the corresponding actual argument when setting that actual with assignment or initialization (see The Out Intent).
Implicit conversions for initialization or assignment are allowed between 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), class types (Implicit Class Conversions), and for generic target types (Implicit Subtype Conversions).
In addition, these implicit conversions can be defined for record types
by implementing init=
and possibly the =
operator between two
types as described in Advanced Copy Initialization and
Function and Operator Overloading. init=
will be called for initialization
as described in Split Initialization and the =
operator will
be invoked for other uses of assignment.
In the event that an =
overload is provided to support assignment
between two types, the compiler will check that a corresponding init=
also exists and emit an error if not. Additionally, if init=
is
provided to initialize one type from another, the corresponding :
overload must also exist. See also Explicit Conversions for more
information on the :
operator. It is possible to provide :
without init=
or to provide init=
without =
.
Example (implementing-assignment.chpl)
Suppose that we have defined a record type to wrap an integer:
record myInteger { var intValue: int; }We might wish to support assignments setting a
myInteger
fromint
. In that event, we can provide the following functions:operator =(ref lhs: myInteger, rhs: int) { lhs.intValue = rhs; } proc myInteger.init=(rhs: int) { this.intValue = rhs; } operator :(from: int, type toType: myInteger) { var tmp: myInteger = from; // invoke the init= above return tmp; }Since we defined
operator =
, it is necessary to also defineinit=
andoperator :
between these types.We can invoke these functions like this:
var a = 1:myInteger; // cast -- invokes operator : var b: myInteger = 2; // initialization -- invokes init= var c: myInteger; c = 3; // split-initialization -- invokes init= var d = new myInteger(); d = 4; // assignment -- invokes operator =
Implicit Conversions for Function Calls¶
An implicit conversion for a function call - also called a coercion -
occurs when the actual argument of a function call 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 of in
or const in
.
Implicit conversions for function calls are allowed between 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), class types (Implicit Class Conversions), and for generic target types (Implicit Subtype Conversions).
Additionally, an implicit conversion for a function call occurs when the
actual type is a subtype of the formal type. This rule applies to in
,
const in
, const ref
, and type
intent formals and includes
generic formal types. See Implicit Subtype Conversions.
Implicit conversions are not applied for actual arguments passed to
ref
formal arguments.
Open issue.
For the
const ref
intent, subtype conversions can be allowed while keeping theconst ref
formal referring to the original actual argument’s value. However, this feature is still under discussion.Open issue.
Should Chapel allow user-defined implicit conversions for function calls? If so, how would the user define them?
Implicit Conversions for Conditionals¶
An implicit conversion for a conditional occurs for the condition of:
a conditional expression,
a conditional statement,
a while-do loop, or
a do-while loop.
In such a condition, the following implicit conversions to bool
are
supported:
An expression of integral type is taken to be
false
if it is0
and istrue
otherwise.An expression of a class type is taken to be
false
if it isnil
and istrue
otherwise.
Other standard types also allow implicit conversion for conditionals as indicated in their documentation.
Open issue.
Should Chapel allow user-defined implicit conversions for conditionals? If so, how would the user define them?
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 allowed explicit conversions are described in the following sections:
conversions among primitive numeric and bool types (see Explicit Numeric Conversions)
tuple to complex (see Explicit Tuple to Complex Conversion)
enumerated types (see Explicit Enumeration Conversions)
class conversions (see Explicit Class Conversions)
range conversions (see Explicit Range Conversions)
domain conversions (see Explicit Domain Conversions)
string to bytes conversions (see Explicit String to Bytes Conversions)
type to string conversions (see Explicit Type to String Conversions)
user-defined explicit conversions (see User-Defined Casts).
The available explicit conversions are a superset of the available implicit conversions for initialization and assignment (Implicit Conversions for Initialization and Assignment), which, in turn, are a superset of the implicit conversions for function calls. As a result, the implicit conversions described in Implicit Conversions are also available as explicit conversions.
An explicit conversion from a type to the same type is allowed for any type. Such a conversion does not change the value of the expression.
Explicit Numeric Conversions¶
Explicit conversions are allowed from bool
or any numeric type to
bytes
or string
, and vice-versa. When converting to bytes
or string
the result will hold the string true
or false
for a bool
, or a representation of the expression’s numerical
value in other cases. When converting from a string
or bytes
,
the reverse occurs, converting the represented value into a numerical
or bool
value. If the string
/bytes
does not represent a
legal value of the given type, an IllegalArgumentError
is thrown.
When a bool
is converted to an int
or uint
, false
converts to the value 0 and true
to 1.
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.
Note
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. 1
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 bool
, 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)"
.
User-Defined Casts¶
An explicit conversion can be defined by implementing operator :
(see
also Function and Operator Overloading). An operator :
should accept two
arguments: the value to convert and the type to convert it to.
Example (implementing-cast.chpl)
Suppose that we have defined a record type to wrap an integer:
record myInteger { var intValue: int; }We might wish to support casts from
myInteger
toint
. In that event, we can provide this cast operator:operator :(from: myInteger, type toType: int) { return from.intValue; }and we can invoke it using the cast syntax like this:
var x = new myInteger(1); var y = x:int;
- 1
When converting to a smaller real type, a loss of precision is expected. Therefore, there is no reason to produce a run-time diagnostic.