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 12 commits
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
7 changes: 5 additions & 2 deletions docs/design/expressions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
## Table of contents

- [Overview](#overview)
- [Implicit conversions](#implicit-conversions)
- [Conversions and casts](#conversions-and-casts)

<!-- tocstop -->

Expand All @@ -29,12 +29,15 @@ fn Foo(a: i32*) -> i32 {
Here, the parameter type `i32*`, the return type `i32`, and the operand `*a` of
the `return` statement are all expressions.

## Implicit conversions
## Conversions and casts

When an expression appears in a context in which an expression of a specific
type is expected, [implicit conversions](implicit_conversions.md) are applied to
convert the expression to the target type.

Expressions can also be converted to a specific type using an
[`as` expression](as_expressions.md).
chandlerc marked this conversation as resolved.
Show resolved Hide resolved

```
fn Bar(n: i32);
fn Baz(n: i64) {
Expand Down
200 changes: 200 additions & 0 deletions docs/design/expressions/as_expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# `as` expressions

<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

<!-- toc -->

## Table of contents

- [Overview](#overview)
- [Precedence and associativity](#precedence-and-associativity)
- [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)

<!-- tocstop -->

## Overview
geoffromer marked this conversation as resolved.
Show resolved Hide resolved

An expression of one type can be explicitly cast to another type by using an
`as` expression:

```
var n: i32 = Get();
var f: f32 = n as f32;
```

An `as` expression can be used to perform any implicit conversion, either when
the context does not imply a destination type or when it is valuable to a reader
of the code to make the conversion explicit. In addition, `as` expressions can
perform safe conversions that nonetheless should not be performed implicitly,
such as lossy conversions or conversions that lose capabilities or change the
way a type would be interpreted.

chandlerc marked this conversation as resolved.
Show resolved Hide resolved
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 _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.

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.

## Precedence and associativity

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

```
var b: bool = true;
// OK
var n: i32 = (b as i1) as i32;
var m: auto = b as (bool as Hashable);
// Error, ambiguous
var m: auto = b as T as U;
```

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

- prefix symbolic operators
- dereference (`*a`)
- negation (`-a`)
- complement (`~a`)
- postfix symbolic operators
- pointer type formation (`T*`),
- function call (`a(...)`),
- 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.

```
// OK
var x: i32* as Comparable;
// OK, `x as (U*)` not `(x as U)*`.
var y: auto = x as U*;

var a: i32;
var b: i32;
// OK, `(a as i64) < (*x as i64)`.
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
if (a as i64 < *x as i64) {}
// Ambiguous: `(a + b) as i64` or `a + (b as i64)`?
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, `i32 as (GetType())`, not `(i32 as GetType)()`.
var e: i32 as GetType();
```

## Built-in types

### Data types

In addition to the [implicit conversions](implicit_conversions.md#data-types),
the following numeric conversion is supported by `as`:
zygoloid marked this conversation as resolved.
Show resolved Hide resolved

- `iN`, `uN`, or `fN` -> `fM`, for any `N` and `M`. Values that cannot be
exactly represented are suitably rounded to one of the two nearest
representable values. Very large finite values may be rounded to an
infinity. NaN values are converted to NaN values.

- `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.

### Compatible types

The following conversion is supported by `as`:

- `T` -> `U` if `T` is
[compatible](../generics/terminology.md#compatible-types) with `U`.

**Future work:** We may need a mechanism to restrict which conversions between
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.
chandlerc marked this conversation as resolved.
Show resolved Hide resolved

## Extensibility

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

```
interface AssumeAs(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)()`.

## Alternatives considered

- [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as)
- [Do not distinguish between safe as and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions)
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
- [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)
- [Allow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions)
zygoloid marked this conversation as resolved.
Show resolved Hide resolved

## References

- [Implicit conversions in C++](https://en.cppreference.com/w/cpp/language/implicit_conversion)
- Proposal
[#845: `as` expressions](https://github.com/carbon-language/carbon-lang/pull/845).
32 changes: 14 additions & 18 deletions docs/design/expressions/implicit_conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Pointer conversions](#pointer-conversions)
- [Pointer conversion examples](#pointer-conversion-examples)
- [Type-of-types](#type-of-types)
- [Semantics](#semantics)
- [Consistency with `as`](#consistency-with-as)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
- [References](#references)
Expand Down Expand Up @@ -245,37 +245,33 @@ can be implicitly converted to the type-of-type `TT2` if `T`
[satisfies the requirements](../generics/details.md#subtyping-between-type-of-types)
of `TT2`.

## Semantics
## Consistency with `as`

An implicit conversion of an expression `E` of type `T` to type `U`, when
permitted, always has the same meaning as the explicit cast expression `E as U`.
Moreover, such an implicit conversion is expected to exactly preserve the value.
For example, `(E as U) as T`, if valid, should be expected to result in the same
value as produced by `E`.

**Note:** The explicit cast expression syntax has not yet been decided. The use
of `E as T` in this document is provisional.
permitted, always has the same meaning as the
[explicit cast expression `E as U`](as_expressions.md). Moreover, because such
an implicit conversion is expected to exactly preserve the value,
`(E as U) as T`, if valid, should be expected to result in the same value as
produced by `E` even if the `as T` cast cannot be performed as an implicit
chandlerc marked this conversation as resolved.
Show resolved Hide resolved
conversion.

## Extensibility

Implicit conversions can be defined for user-defined types such as
[classes](../classes.md) by implementing the `ImplicitAs` interface:
[classes](../classes.md) by implementing the `ImplicitAs` interface, which
extends
[the `As` interface used to implement `as` expressions](as_expressions.md#extensibility):

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

When attempting to implicitly convert an expression `x` to type `U`, the
expression is rewritten to `x.(ImplicitAs(U).Convert)()`.

**Note:** The `As` interface is intended to be used as the implementation
vehicle for explicit casts: `x as U` would be rewritten as
`x.(As(U).Convert)()`. However, the explicit cast expression syntax has not yet
been decided, so this rewrite is provisional.

Note that implicit conversions are not transitive. Even if an
`impl A as ImplicitAs(B)` and an `impl B as ImplicitAs(C)` are both provided, an
expression of type `A` cannot be implicitly converted to type `C`. Allowing
Expand Down
2 changes: 1 addition & 1 deletion docs/design/generics/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -1572,7 +1572,7 @@ addition to using the same data representation, they both implement one
interface, `Hashable`, and use the same implementation for that interface. The
one difference between them is that `Song as Hashable` may be implicitly
converted to `Song`, which implements interface `Printable`, and
`PlayableSong as Hashable` may be implicilty converted to `PlayableSong`, which
`PlayableSong as Hashable` may be implicitly converted to `PlayableSong`, which
implements interface `Media`. This means that it is safe to convert between
`HashMap(Song, Int) == HashMap(Song as Hashable, Int)` and
`HashMap(PlayableSong, Int) == HashMap(PlayableSong as Hashable, Int)` (though
Expand Down
2 changes: 2 additions & 0 deletions docs/design/lexical_conventions/words.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following words are interpreted as keywords:
- `alias`
- `and`
- `api`
- `as`
- `assume_as`
- `auto`
- `base`
- `break`
Expand Down
Loading