Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

as expressions #845

Merged
merged 19 commits into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 34 additions & 56 deletions docs/design/expressions/as_expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Built-in types](#built-in-types)
- [Data types](#data-types)
- [Compatible types](#compatible-types)
- [Pointer conversions](#pointer-conversions)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
- [References](#references)
Expand All @@ -41,23 +40,33 @@ way a type would be interpreted.

As guidelines, an `as` conversion should be permitted when:

- The conversion is _safe_: it produces a well-defined output value for each
input value.
- The conversion is _complete_: it produces a well-defined output value for
each input value.
- The conversion is _unsurprising_: the resulting value is the expected value
in the destination type.

In cases where a cast is only defined for a subset of the possible inputs, an
`assume_as` expression can be used. An `assume_as` expression behaves like an
`as` expression, except that the domain of the conversion is narrower than the
entire input type, so the conversion is not safe as defined above.
For example:

- A conversion from `fM` to `iN` is not complete, because it is not defined
for input values that are out of the range of the destination type, such as
infinities or, if `N` is too small, large finite values.
- A conversion from `iM` to `iN`, where `N` < `M`, is either not complete or
not unsurprising, because there is more than one possible expected behavior
for an input value that is not within the destination type, and those
behaviors are not substantially the same -- we could perform two's
complement wrapping, saturate, or produce undefined behavior analogous to
arithmetic overflow.
- A conversion from `iM` to `fN` can be unsurprising, because even though
there may be a choice of which way to round, the possible values are
substantially the same.

It is possible for user-defined types to [extend](#extensibility) the set of
valid explicit casts that can be performed by `as` and `assume_as`. Such
extensions are expected to follow these guidelines.
valid explicit casts that can be performed by `as`. Such extensions are expected
to follow these guidelines.

## Precedence and associativity

`as` and `assume_as` expressions are non-associative.
`as` expressions are non-associative.

```
var b: bool = true;
Expand All @@ -68,8 +77,8 @@ var m: auto = b as (bool as Hashable);
var m: auto = b as T as U;
```

The `as` and `assume_as` operators have lower precedence than operators that
visually bind tightly:
The `as` operator has lower precedence than operators that visually bind
tightly:

- prefix symbolic operators
- dereference (`*a`)
Expand All @@ -81,9 +90,8 @@ visually bind tightly:
- array indexing (`a[...]`), and
- member access (`a.m`).

The `as` and `assume_as` operators have higher precedence than assignment and
comparison. They are unordered with respect to binary arithmetic, bitwise
operators, unary `not`, and each other.
The `as` operator has higher precedence than assignment and comparison. It is
unordered with respect to binary arithmetic, bitwise operators, and unary `not`.

```
// OK
Expand All @@ -100,8 +108,10 @@ var c: i32 = a + b as i64;
// Ambiguous: `(a as i64) + b` or `a as (i64 + b)`?
var d: i32 = a as i64 + b;

// OK, `(-a) assume_as u64`, not `-(a assume_as u64)`.
var u: u64 = -a assume_as u64;
// OK, `(-a) as f64`, not `-(a as f64)`.
// Unfortunately, the former is undefined if `a` is `i32.MinValue()`;
// the latter is not.
var u: f64 = -a as f64;

// OK, `i32 as (GetType())`, not `(i32 as GetType)()`.
var e: i32 as GetType();
Expand All @@ -122,24 +132,13 @@ the following numeric conversions are supported by `as`:
- `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1`
(or to `-1` for `i1`).

The following additional numeric conversions are supported by `assume_as`:

- `iN` or `uN` -> `iM` or `uM`, for any `N` and `M`. It is a programming error
if the source value cannot be represented in the destination type.

**TODO:** Once we have a two's complement truncation operation with defined
behavior on overflow, link to it from here as an alternative.

- `fN` -> `iM`, for any `N` and `M`. Values that cannot be exactly represented
are suitably rounded to one of the two nearest representable values. It is a
programming error if the source value does not round to an integer that can
be represented in the destination type.

Conversions from numeric types to `bool` are not supported with `as`; instead of
using `as bool`, such conversions can be performed with `!= 0`.

**Note:** The precise rounding rules for these conversions have not yet been
decided.
Lossy conversions between `iN` or `uN` and `iM` or `uM` are not supported with
`as`, and similarly conversions from `fN` to `iM` are not supported.

**Future work:** Add mechanisms to perform these conversions.

### Compatible types

Expand All @@ -152,44 +151,23 @@ The following conversion is supported by `as`:
adapters are permitted and which code can perform them. Some of the conversions
permitted by this rule may only be allowed in certain contexts.

### Pointer conversions

The following pointer conversion is supported by `assume_as`:

- `T*` -> `U*` if `U` is a subtype of `T`.

This cast converts in the opposite direction to the corresponding
[implicit conversion](implicit_conversions.md#pointer-conversions). It is a
programming error if the source pointer does not point to a `U` object.

**Note:** `assume_as` cannot convert between unrelated pointer types, because
there are no input values for which the conversion would produce a well-defined
output value. Separate facilities will be provided for reinterpreting memory as
a distinct type.

## Extensibility

Explicit casts can be defined for user-defined types such as
[classes](../classes.md) by implementing the `As` or `AssumeAs` interface:
[classes](../classes.md) by implementing the `As` interface:

```
interface AssumeAs(Dest:! Type) {
interface As(Dest:! Type) {
fn Convert[me: Self]() -> Dest;
}
interface As(Dest:! Type) extends AssumeAs(Dest) {
// Inherited from AssumeAs(Dest):
// fn Convert[me: Self]() -> Dest;
}
```

The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression
`x assume_as U` is rewritten to `x.(AssumeAs(U).Convert)()`.
The expression `x as U` is rewritten to `x.(As(U).Convert)()`.

## Alternatives considered

- [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as)
- [Do not distinguish between `as` and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions)
- [Use a different name for `assume_as`](/docs/proposals/p0845.md#different-name-for-assume_as)
- [Allow `iN as bool`](/docs/proposals/p0845.md#integer-to-bool-conversions)
- [Disallow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions)

Expand Down
1 change: 0 additions & 1 deletion docs/design/lexical_conventions/words.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ The following words are interpreted as keywords:
- `and`
- `api`
- `as`
- `assume_as`
- `auto`
- `base`
- `break`
Expand Down
Loading