-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Implicit conversions #820
Merged
zygoloid
merged 17 commits into
carbon-language:trunk
from
zygoloid:proposal-implicit-conversions
Sep 21, 2021
Merged
Implicit conversions #820
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c74b175
Filling out template with PR 820
zygoloid e6f848a
First draft.
zygoloid 8e0b017
Improve wording; permit implicit conversion between facet types.
zygoloid d9f5ebd
Remove unnecessary TODO.
zygoloid 62f02af
Apply suggestions from code review
zygoloid c4f5162
We can fit a one-bit-wider signed int into a floating-point type than we
zygoloid 3b1d931
Add link to definition of 'compatible type'.
zygoloid 5a1b482
Update docs/design/expressions/implicit_conversions.md
zygoloid 3d9ec8b
Attempt to clarify terminology. Add more examples.
zygoloid 90a5643
Allow equivalent type conversions on the pointee of a pointer type as
zygoloid 6ecb91a
Responses to review comments.
zygoloid bd6d034
Fix typo.
zygoloid 033a02a
Add more detail and examples for lossless and semantics-preserving.
zygoloid 1044c5b
Add alternative considered for applying implicit conversions
zygoloid 111bfa8
Apply suggestions from code review
zygoloid 0835585
Merge branch 'trunk' into proposal-implicit-conversions
zygoloid 6100230
Reflow for pre-commit.
zygoloid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# 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) | ||
- [Implicit conversions](#implicit-conversions) | ||
|
||
<!-- tocstop --> | ||
|
||
## Overview | ||
|
||
Expressions are the portions of Carbon syntax that produce values. Because types | ||
in Carbon are values, this includes anywhere that a type is specified. | ||
|
||
``` | ||
fn Foo(a: i32*) -> i32 { | ||
return *a; | ||
} | ||
``` | ||
|
||
Here, the parameter type `i32*`, the return type `i32`, and the operand `*a` of | ||
the `return` statement are all expressions. | ||
|
||
## Implicit conversions | ||
|
||
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. | ||
|
||
``` | ||
fn Bar(n: i32); | ||
fn Baz(n: i64) { | ||
// OK, same as Bar(n as i32) | ||
Bar(n); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
# Implicit conversions | ||
|
||
<!-- | ||
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) | ||
- [Properties of implicit conversions](#properties-of-implicit-conversions) | ||
- [Lossless](#lossless) | ||
- [Semantics-preserving](#semantics-preserving) | ||
- [Examples](#examples) | ||
- [Built-in types](#built-in-types) | ||
- [Data types](#data-types) | ||
- [Equivalent types](#equivalent-types) | ||
- [Pointer conversions](#pointer-conversions) | ||
- [Pointer conversion examples](#pointer-conversion-examples) | ||
- [Type-of-types](#type-of-types) | ||
- [Semantics](#semantics) | ||
- [Extensibility](#extensibility) | ||
- [Alternatives considered](#alternatives-considered) | ||
- [References](#references) | ||
|
||
<!-- tocstop --> | ||
|
||
## Overview | ||
|
||
When an expression appears in a context in which an expression of a specific | ||
type is expected, the expression is implicitly converted to that type if | ||
possible. | ||
|
||
For [built-in types](#built-in-types), implicit conversions are permitted when: | ||
|
||
- The conversion is [_lossless_](#lossless): every possible value for the | ||
source expression converts to a distinct value in the target type. | ||
- The conversion is [_semantics-preserving_](#semantics-preserving): | ||
corresponding values in the source and destination type have the same | ||
abstract meaning. | ||
|
||
These rules aim to ensure that implicit conversions are unsurprising: the value | ||
that is provided as the operand of an operation should match how that operation | ||
interprets the value, because the identity and abstract meaning of the value are | ||
preserved by any implicit conversions that are applied. | ||
|
||
It is possible for user-defined types to [extend](#extensibility) the set of | ||
valid implicit conversions. Such extensions are expected to also follow these | ||
rules. | ||
|
||
## Properties of implicit conversions | ||
|
||
### Lossless | ||
|
||
We expect implicit conversion to never lose information: if two values are | ||
distinguishable before the conversion, they should generally be distinguishable | ||
after the conversion. It should be possible to define a conversion in the | ||
opposite direction that restores the original value, but such a conversion is | ||
not expected to be provided in general, and might be computationally expensive. | ||
|
||
Because an implicit conversion is converting from a narrower type to a wider | ||
type, implicit conversions do not necessarily preserve static information about | ||
the source value. | ||
|
||
### Semantics-preserving | ||
|
||
We expect implicit conversions to preserve the meaning of converted values. The | ||
assessment of this criterion will necessarily be subjective, because the | ||
meanings of values generally live in the mind of the programmer rather than in | ||
the program text. However, the semantic interpretation is expected to be | ||
consistent from one conversion to another, so we can provide a test: if multiple | ||
paths of implicit conversions from a type `A` to a type `B` exist, and the same | ||
value of type `A` would convert to different values of type `B` along different | ||
paths, then at least one of those conversions must not be semantics-preserving. | ||
|
||
A semantics-preserving conversion does not necessarily preserve the meaning of | ||
particular syntax when applied to the value. The same syntax may map to | ||
different operations in the new type. For example, division may mean different | ||
things in integer and floating-point types, and member access may find different | ||
members in a derived class pointer versus in a base class pointer. | ||
|
||
### Examples | ||
|
||
Conversion from `i32` to `Vector(int)` by forming a vector of N zeroes is | ||
lossless but not semantics-preserving. | ||
|
||
Conversion from `i32` to `f32` by rounding to the nearest representable value is | ||
semantics-preserving but not lossless. | ||
|
||
Conversion from `String` to `StringView` is lossless, because we can compute the | ||
`String` value from the `StringView` value, and semantics-preserving because the | ||
string value denoted is the same. Conversion in the other direction may or may | ||
not be semantics-preserving depending on whether we consider the address to be a | ||
salient part of a `StringView`'s value. | ||
|
||
## Built-in types | ||
|
||
### Data types | ||
|
||
The following implicit numeric conversions are available: | ||
|
||
- `iN` or `uN` -> `iM` if `M` > `N` | ||
- `uN` -> `uM` if `M` > `N` | ||
- `fN` -> `fM` if `M` > `N` | ||
- `iN` or `uN` -> `fM` if every value of type `iN` or `uN` can be represeted | ||
in `fM`: | ||
- `i12` or `u11` (or smaller) -> `f16` | ||
- `i25` or `u24` (or smaller) -> `f32` | ||
- `i54` or `u53` (or smaller) -> `f64` | ||
- `i65` or `u64` (or smaller) -> `f80` (x86 only) | ||
- `i114` or `u113` (or smaller) -> `f128` (if available) | ||
- `i238` or `u237` (or smaller) -> `f256` (if available) | ||
|
||
In each case, the numerical value is the same before and after the conversion. | ||
An integer zero is translated into a floating-point positive zero. | ||
|
||
An integer constant can be implicitly converted to any type `iM`, `uM`, or `fM` | ||
in which that value can be exactly represented. A floating-point constant can be | ||
implicitly converted to any type `fM` in which that value is between the least | ||
representable finite value and the greatest representable finite value | ||
(inclusive), and does not fall exactly half-way between two representable | ||
values, and converts to the nearest representable finite value. | ||
|
||
The above conversions are also precisely those that C++ considers non-narrowing, | ||
except: | ||
|
||
- Carbon also permits integer to floating-point conversions in more cases. The | ||
most important of these is that Carbon permits `i32` to be implicitly | ||
converted to `f64`. Lossy conversions, such as from `i32` to `f32`, are not | ||
permitted. | ||
|
||
- What Carbon considers to be an integer constant or floating-point constant | ||
may differ from what C++ considers to be a constant expression. | ||
|
||
**Note:** We have not yet decided what will qualify as a constant in this | ||
context, but it will include at least integer and floating-point literals, | ||
with optional enclosing parentheses. It is possible that such constants will | ||
have singleton types; see issue | ||
[#508](https://github.com/carbon-language/carbon-lang/issues/508). | ||
|
||
### Equivalent types | ||
|
||
The following conversion is available: | ||
|
||
- `T` -> `U` if `T` is equivalent to `U` | ||
|
||
Two types are equivalent if they can be used interchangeably, implicitly: they | ||
have the same set of values with the same meaning and the same representation, | ||
with the same set of capabilities and constraints, where the only difference is | ||
how the type interprets operations on values of that type. | ||
|
||
`T` is equivalent to `U` if: | ||
|
||
- `T` is the same type as `U`, or | ||
- `T` is the facet type `U as SomeInterface`, or | ||
- `U` is the facet type `T as SomeInterface`, or | ||
- `T` is `A*`, `U` is `B*`, and `A` is equivalent to `B`, or | ||
- for some type `V`, `T` is equivalent to `V` and `V` is equivalent to `U`. | ||
|
||
**Note:** More type equivalence rules are expected to be added over time. | ||
|
||
A prerequisite for types being equivalent is that they are | ||
[compatible](../generics/terminology.md#compatible-types), and in particular | ||
that they have the same set of values and the same representation for those | ||
values. However, types being compatible does not imply that an implicit | ||
conversion, or even an explicit cast, between those types is necessarily valid. | ||
This is because the type of a value models not only the representation of the | ||
value but also the capabilities that a user of the value has to interact with | ||
the value. Two compatible types may expose different capabilities, such as the | ||
capability to mutate the object or to access its implementation details, and | ||
conversions between such types may require an explicit cast if the conversion is | ||
possible at all. | ||
|
||
### Pointer conversions | ||
|
||
The following pointer conversion is available: | ||
|
||
- `T*` -> `U*` if `T` is a subtype of `U`. | ||
chandlerc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`T` is a subtype of `U` if: | ||
|
||
- `T` is equivalent to `U`, as described above, or | ||
- `T` is equivalent to a class derived from a class equivalent to `U`. | ||
|
||
**Note:** More type subtyping rules are expected to be added over time. | ||
|
||
`T*` is not necessarily a subtype of `U*` even if `T` is a subtype of `U`. For | ||
example, we can convert `Derived*` to `Base*`, but cannot convert `Derived**` to | ||
`Base**` because that would allow storing a `Derived2*` into a `Derived*`: | ||
|
||
``` | ||
abstract class Base {} | ||
class Derived extends Base {} | ||
class Derived2 extends Base {} | ||
var d2: Derived2 = {}; | ||
var p: Derived*; | ||
var q: Derived2* = &d2; | ||
var r: Base** = &p; | ||
// Bad: would store q to p. | ||
*r = q; | ||
``` | ||
|
||
**Note:** If we add `const` qualification, we could treat `const T*` as a | ||
subtype of `const U*` if `T` is a subtype of `U`, and could treat `T` as a | ||
subtype of `const T`. | ||
|
||
#### Pointer conversion examples | ||
|
||
With these classes: | ||
|
||
``` | ||
base class C; | ||
let F: auto = C as Hashable; | ||
class D extends C; | ||
``` | ||
|
||
These implicit pointer conversions are permitted: | ||
|
||
- `D*` -> `C*`: `D` is a subtype of `C` | ||
- `F*` -> `C*`: `F` is equivalent to `C`, so `F` is a subtype of `C` | ||
- `C*` -> `F*`: `C` is equivalent to `F`, so `C` is a subtype of `F` | ||
- `F**` -> `C**`: `F` is equivalent to `C`, so `F*` is equivalent to `C*`, so | ||
`F*` is a subtype of `C*` | ||
- `D*` -> `F*`: `D` is derived from `C` and `C` is equivalent to `D`, so `D` | ||
is a subtype of `F` | ||
|
||
These implicit pointer conversions are disallowed: | ||
|
||
- `C*` -> `D*`: `C` is not a subtype of `D` | ||
- `D**` -> `C**`: `D*` is not a subtype of `C*` | ||
|
||
Note that "equivalent to" means we can freely convert back and forwards; the | ||
difference in the types is just changing which operations are surfaced, not | ||
changing anything about the interpretation or switching between different | ||
abstractions. In contrast, "subtype of" permits conversion from a more specific | ||
type to a more general type, so the reverse conversion is not necessarily valid. | ||
|
||
### Type-of-types | ||
|
||
A type `T` with [type-of-type](../generics/terminology.md#type-of-type) `TT1` | ||
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 | ||
|
||
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. | ||
|
||
## Extensibility | ||
|
||
Implicit conversions can be defined for user-defined types such as | ||
[classes](../classes.md) by implementing the `ImplicitAs` interface: | ||
|
||
``` | ||
interface As(Dest:! Type) { | ||
fn Convert[me: Self]() -> Dest; | ||
} | ||
chandlerc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
transitivity would introduce the risk of ambiguity issues as code evolves and | ||
would in general require a search of a potentially unbounded set of intermediate | ||
types. | ||
geoffromer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Alternatives considered | ||
|
||
- [Provide lossy and non-semantics-preserving implicit conversions from C++](/docs/proposals/p0820.md#c-conversions) | ||
- [Provide no implicit conversions](/docs/proposals/p0820.md#no-conversions) | ||
- [Provide no extensibility](/docs/proposals/p0820.md#no-extensibility) | ||
- [Apply implicit conversions transitively](/docs/proposals/p0820.md#transitivity) | ||
|
||
## References | ||
|
||
- [Implicit conversions in C++](https://en.cppreference.com/w/cpp/language/implicit_conversion) | ||
- Proposal | ||
[#820: implicit conversions](https://github.com/carbon-language/carbon-lang/pull/820). |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phrase "used interchangably" seems a bit confusing here. The subsequent explanation is clear, but maybe just jump to that?