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

Arithmetic expressions #1083

Merged
merged 44 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
45dc57a
Add precedence docs and scripts
jonmeow Feb 9, 2022
fb8ce8a
Small comment edit
jonmeow Feb 9, 2022
475ce6d
Switch away from graphviz module since it doesn't add much
jonmeow Feb 9, 2022
e16a380
Bump up the vertical margin due to arrows
jonmeow Feb 9, 2022
ce621d6
Remove redundancy
jonmeow Feb 9, 2022
2b312be
Arithmetic expressions: design updates, first pass.
zygoloid Feb 11, 2022
aed1c3c
Merge branch 'trunk' into op-prec
jonmeow Feb 15, 2022
970c80a
Switch to mermaid
jonmeow Feb 15, 2022
c1d013f
Fixes
jonmeow Feb 15, 2022
d5a2ba7
Recluster sentences
jonmeow Feb 15, 2022
053c065
Rephrase
jonmeow Feb 15, 2022
05010e0
Adjust spacing for readability
jonmeow Feb 15, 2022
edab4a6
Remove arrow on edges
jonmeow Feb 15, 2022
1725c75
Explanation
jonmeow Feb 16, 2022
9250a35
quote
jonmeow Feb 16, 2022
e6c44fb
Rephrase a little more
jonmeow Feb 16, 2022
0992b07
Rephrase once more
jonmeow Feb 16, 2022
6e85957
For example phrasing
jonmeow Feb 16, 2022
c42351a
transitive
jonmeow Feb 16, 2022
444e713
Proposal and design updates.
zygoloid Feb 17, 2022
5bad4e8
Expand rationale a little.
zygoloid Feb 17, 2022
6a87b40
Merge PR#1070.
zygoloid Feb 17, 2022
7b3e804
Add arithmetic operators and member access to precedence diagram.
zygoloid Feb 17, 2022
6c4d427
Per as_expressions.md, don't allow `as` inside `not`.
zygoloid Feb 17, 2022
0466790
Add a precedence diagram excerpt to the arithmetic documentation.
zygoloid Feb 17, 2022
548c01f
Remove incorrect description of behavior of multiple operators in the
zygoloid Feb 17, 2022
ec3a9df
Add struct literals an if/else to precedence diagram.
zygoloid Feb 17, 2022
017042b
Use rounder brackets for terminal nodes.
zygoloid Feb 17, 2022
f66dbf1
Fix reversed precedence diagram.
zygoloid Feb 17, 2022
a2de7e5
Split `and` and `or` into separate precedence groups, each of which is
zygoloid Feb 17, 2022
84f014f
Fix mermaid syntax.
zygoloid Feb 17, 2022
4293a73
Respond to review comments.
zygoloid Feb 18, 2022
f10e868
Fix markdown syntax.
zygoloid Feb 23, 2022
853a326
Address some review comments.
zygoloid Feb 24, 2022
680cc4d
Merge branch 'trunk' into proposal-arithmetic
zygoloid Feb 26, 2022
eadf24f
Fix up after merge.
zygoloid Feb 26, 2022
0be487d
More consistent formatting.
zygoloid Feb 26, 2022
9d57a96
Fix up confused arrow directions.
zygoloid Feb 26, 2022
db8a9fd
Encourage `not` to sit closer to `and` and `or`.
zygoloid Feb 26, 2022
f106490
Merge branch 'trunk' into proposal-arithmetic
zygoloid Mar 4, 2022
c5154aa
Respond to review comments.
zygoloid Mar 5, 2022
fd8d364
Update proposals/p1083.md
zygoloid Mar 16, 2022
5b1160d
Updates from review comments.
zygoloid Mar 16, 2022
200ca3c
Fix constraint extends syntax
zygoloid Mar 25, 2022
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
85 changes: 82 additions & 3 deletions docs/design/expressions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

- [Overview](#overview)
- [Operators](#operators)
- [Precedence](#precedence)
- [Conversions and casts](#conversions-and-casts)
- [`if` expressions](#if-expressions)
- [Alternatives considered](#alternatives-considered)
- [References](#references)

<!-- tocstop -->

Expand All @@ -38,15 +41,74 @@ Most expressions are modeled as operators:
| Category | Operator | Syntax | Function |
| ---------- | ------------------------------- | --------- | --------------------------------------------------------------------- |
| Conversion | [`as`](as_expressions.md) | `x as T` | Converts the value `x` to the type `T`. |
| Logical | [`and`](logical_operators.md) | `x and y` | A short-circuiting logical AND: `true` if both operands are `true`. |
| Logical | [`or`](logical_operators.md) | `x or y` | A short-circuiting logical OR: `true` if either operand is `true`. |
| Logical | [`not`](logical_operators.md) | `not x` | Logical NOT: `true` if the operand is `false`. |
| Arithmetic | [`-`](arithmetic.md) (unary) | `-x` | The negative of `x`. |
geoffromer marked this conversation as resolved.
Show resolved Hide resolved
| Arithmetic | [`+`](arithmetic.md) | `x + y` | The sum of `x` and `y`. |
| Arithmetic | [`-`](arithmetic.md) (binary) | `x - y` | The difference of `x` and `y`. |
| Arithmetic | [`*`](arithmetic.md) | `x * y` | The product of `x` and `y`. |
| Arithmetic | [`/`](arithmetic.md) | `x / y` | `x` divided by `y`, or the quotient thereof. |
| Arithmetic | [`%`](arithmetic.md) | `x % y` | `x` modulo `y`. |
| Comparison | [`==`](comparison_operators.md) | `x == y` | Equality: `true` if `x` is equal to `y`. |
| Comparison | [`!=`](comparison_operators.md) | `x != y` | Inequality: `true` if `x` is not equal to `y`. |
| Comparison | [`<`](comparison_operators.md) | `x < y` | Less than: `true` if `x` is less than `y`. |
| Comparison | [`<=`](comparison_operators.md) | `x <= y` | Less than or equal: `true` if `x` is less than or equal to `y`. |
| Comparison | [`>`](comparison_operators.md) | `x > y` | Greater than: `true` if `x` is greater than to `y`. |
| Comparison | [`>=`](comparison_operators.md) | `x >= y` | Greater than or equal: `true` if `x` is greater than or equal to `y`. |
| Logical | [`and`](logical_operators.md) | `x and y` | A short-circuiting logical AND: `true` if both operands are `true`. |
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
| Logical | [`or`](logical_operators.md) | `x or y` | A short-circuiting logical OR: `true` if either operand is `true`. |
| Logical | [`not`](logical_operators.md) | `not x` | Logical NOT: `true` if the operand is `false`. |

### Precedence

Operators have a partial
[precedence ordering](https://en.wikipedia.org/wiki/Order_of_operations).
Expressions using operators that lack a relative ordering must be disambiguated
by the developer, for example by adding parentheses; when a program's meaning
depends on an undefined relative ordering of two operators, it will be rejected
due to ambiguity. Precedence orderings will only be added when it's reasonable
to expect most developers to understand the precedence without parentheses.

The precedence diagram is defined thusly:

```mermaid
graph TD
parens["(...)"] --> postfix
struct_literal["{.a = x, .b = y}<br> {.a: T, .b: U}"] --> postfix
postfix["x.y"] --> not & negation
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
negation["-x"] --> multiplicative & modulo & as
as["x as T"] --> comparison
not["not x"] --> and & or
multiplicative>"x * y<br> x / y"] --> additive
additive>"x + y<br> x - y"] --> comparison
modulo["x % y"] --> comparison
comparison["x == y<br> x != y<br> x < y<br> x <= y<br> x > y<br> x >= y"] --> and & or
and>"x and y"] --> expression_statement & if_else
or>"x or y"] --> expression_statement & if_else
expression_statement(["x ;"])
if_else["if x then y else z"] --> subexpression
subexpression(["parenthesized subexpression"])
```

The diagram's attributes are:

- Edges indicate a relative ordering: given an edge A --> B, it means that A
is higher precedence than B. This is also transitive, such as with A and C
in A --> B --> C.

- Higher precedence operators are higher in the graph.

- For example, `not x or y` is valid because there is an arrow from `not`
to `or`, so `not` is higher precedence than `or` and can be used inside
`or` expressions without parentheses. The parenthesized equivalent is
`((not x) or y)`.

- Shapes indicate
[associativity](https://en.wikipedia.org/wiki/Operator_associativity):

```mermaid
graph TD
non["Non-associative"]
left>"Left associative"]
```

## Conversions and casts

Expand Down Expand Up @@ -76,3 +138,20 @@ fn Run(args: Span(StringView)) {
```

`if` expressions are analogous to `?:` ternary expressions in C and C++.

## Alternatives considered

Other expression documents will list more references; this lists references not
noted elsewhere.
josh11b marked this conversation as resolved.
Show resolved Hide resolved

- [Total order](/proposals/p0555.md#total-order)
- [Different precedence for different operands](/proposals/p0555.md#different-precedence-for-different-operands)
- [Require less than a partial order](/proposals/p0555.md#require-less-than-a-partial-order)

## References

Other expression documents will list more references; this lists references not
noted elsewhere.

- Proposal
[#555: Operator precedence](https://github.com/carbon-language/carbon-lang/pull/555).
265 changes: 265 additions & 0 deletions docs/design/expressions/arithmetic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Arithmetic

<!--
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)
- [Integer types](#integer-types)
- [Floating-point types](#floating-point-types)
- [Overflow and other error conditions](#overflow-and-other-error-conditions)
- [Strings](#strings)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
- [References](#references)

<!-- tocstop -->

## Overview

Carbon provides a conventional set of arithmetic operators:

```
var a: i32 = 5;
var b: i32 = 3;

// -5
var negative: i32 = -a;
// 8
var sum: i32 = a + b;
// 2
var difference: i32 = a - b;
// 15
var product: i32 = a * b;
// 1
var quotient: i32 = a / b;
// 2
var remainder: i32 = a % b;
josh11b marked this conversation as resolved.
Show resolved Hide resolved
```

These operators have predefined meanings for some of Carbon's
[built-in types](#built-in-types).

User-defined types can define the meaning of these operations by
[implementing an interface](#extensibility) provided as part of the Carbon
standard library.

## Precedence and associativity

```mermaid
graph TD
negation["-x"] --> multiplicative & modulo
multiplicative>"x * y<br> x / y"] --> additive
additive>"x + y<br> x - y"]
modulo["x % y"]
```

<small>[Instructions for reading this diagram.](README.md#precedence)</small>

Binary `+` and `-` can be freely mixed, and are left-associative.

```
// -2, same as `((1 - 2) + 3) - 4`.
var n: i32 = 1 - 2 + 3 - 4;
```

Binary `*` and `/` can be freely mixed, and are left-associative.

```
// 0.375, same as `((1.0 / 2.0) * 3.0) / 4.0`.
var m: f32 = 1.0 / 2.0 * 3.0 / 4.0;
```

Unary `-` has higher precedence than binary `*`, `/`, and `%`. Binary `*` and
`/` have higher precedence than binary `+` and `-`.

```
// 5, same as `(-1) + ((-2) * (-3))`.
var x: i32 = -1 + -2 * -3;
// Error, parentheses required: no precedence order between `+` and `%`.
var y: i32 = 2 + 3 % 5;
```

## Built-in types

For binary operators, if the operands have different built-in types, they are
chandlerc marked this conversation as resolved.
Show resolved Hide resolved
converted as follows:

- If the types are `uN` and `uM`, or they are `iN` and `iM`, the operands are
converted to the larger type.
- If one type is `iN` and the other type is `uM`, and `M` < `N`, the `uM`
operand is converted to `iN`.
- If one type is `fN` and the other type is `iM` or `uM`, and there is an
[implicit conversion](implicit_conversions.md#data-types) from the integer
type to `fN`, then the integer operand is converted to `fN`.

More broadly, if one operand is of built-in type and the other operand can be
implicitly converted to that type, then it is.
josh11b marked this conversation as resolved.
Show resolved Hide resolved

A built-in arithmetic operation is performed if, after the above conversion
step, the operands have the same built-in type. The result type is that type.
The result type is never wider than the operands, and the conversions applied to
the operands are always lossless, so arithmetic between a wider unsigned integer
type and a narrower signed integer is not defined.

Although the conversions are always lossless, the arithmetic may still
[overflow](#overflow-and-other-error-conditions).

### Integer types

Signed and unsigned integer types support all the arithmetic operators.

Signed integer arithmetic produces the usual mathematical result. Unsigned
integer arithmetic in `uN` wraps around modulo 2<sup>`N`</sup>.

Division truncates towards zero. The result of the `%` operator is defined by
the equation `a % b == a - a / b * b`.

### Floating-point types

Floating-point types support all the arithmetic operators other than `%`.
Floating-point types in Carbon have IEEE 754 semantics, use the round-to-nearest
rounding mode, and do not set any floating-point exception state.

### Overflow and other error conditions

Integer arithmetic is subject to two classes of error condition:

- Overflow, where the resulting value is too large to be represented in the
type, or, for `%`, when the implied multiplication overflows.
- Division by zero.

Unsigned integer arithmetic cannot overflow, but division by zero is still an
error.

Floating-point arithmetic follows IEEE 754 rules: overflow results in ±∞, and
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
division by zero results in either ±∞ or, for 0.0 / 0.0, a quiet NaN.

**Note:** All arithmetic operators can overflow for signed integer types. For
example, given a value `v: iN` that is the least possible value for its type,
`-v`, `v + v`, `v - 1`, `v * 2`, `v / -1`, and `v % -1` all result in overflow.

Signed integer overflow and signed or unsigned integer division by zero are
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
programming errors:

- In a development build, they will be caught immediately at runtime.
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
- In a performance build, the optimizer can assume that such conditions don't
occur.
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
- In a hardened build, overflow and division by zero do not result in
undefined behavior. On overflow, either a valid but incorrect value will be
produced, or the program will be aborted. The runtime error might not in all
jonmeow marked this conversation as resolved.
Show resolved Hide resolved
cases occur immediately -- for example, multiple overflow checks might be
combined into one. The behavior of a division by zero is a runtime error
that aborts the program.

**TODO:** In a hardened build, should we prefer to trap on overflow, give a
two's complement result, or produce zero? Using zero may defeat some classes of
exploit, but comes at a code size and performance cost.

### Strings
josh11b marked this conversation as resolved.
Show resolved Hide resolved

Binary `+` performs string concatenation.
jonmeow marked this conversation as resolved.
Show resolved Hide resolved

## Extensibility
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should be checked in as a Carbon source file, see https://discord.com/channels/655572317891461132/707150492370862090/943638327817547816

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I also think this can be a follow-up.


Arithmetic operators can be provided for user-defined types by implementing the
following family of interfaces:

```
// Unary `-`.
interface Negatable {
josh11b marked this conversation as resolved.
Show resolved Hide resolved
let Result:! Type = Self;
fn Negate[me: Self]() -> Result;
}
```

```
// Binary `+`.
interface AddableWith(U:! Type) {
josh11b marked this conversation as resolved.
Show resolved Hide resolved
let Result:! Type = Self;
fn Add[me: Self](other: U) -> Result;
}
constraint Addable extends AddableWith(Self) where .Result = Self {}
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
```

```
// Binary `-`.
interface SubtractableWith(U:! Type) {
let Result:! Type = Self;
fn Subtract[me: Self](other: U) -> Result;
}
constraint Subtractable extends SubtractableWith(Self) where .Result = Self {}
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
```

```
// Binary `*`.
interface MultipliableWith(U:! Type) {
let Result:! Type = Self;
fn Multiply[me: Self](other: U) -> Result;
}
constraint Multipliable extends MultipliableWith(Self) where .Result = Self {}
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
```

```
// Binary `/`.
interface DividableWith(U:! Type) {
let Result:! Type = Self;
fn Divide[me: Self](other: U) -> Result;
}
constraint Dividable extends DividableWith(Self) where .Result = Self {}
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
```

```
// Binary `%`.
interface ModuloWith(U:! Type) {
let Result:! Type = Self;
fn Mod[me: Self](other: U) -> Result;
}
constraint Modulo extends ModuloWith(Self) where .Result = Self {}
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
```

Given `x: T` and `y: U`:

- The expression `-x` is rewritten to `x.(Negatable.Negate)()`.
- The expression `x + y` is rewritten to `x.(AddableWith(U).Add)(y)`.
- The expression `x - y` is rewritten to
`x.(SubtractableWith(U).Subtract)(y)`.
- The expression `x * y` is rewritten to
`x.(MultipliableWith(U).Multiply)(y)`.
- The expression `x / y` is rewritten to `x.(DividableWith(U).Divide)(y)`.
- The expression `x % y` is rewritten to `x.(ModuloWith(U).Mod)(y)`.

Implementations of these interfaces are provided for built-in types as necessary
to give the semantics described above.

## Alternatives considered

- [Use a sufficiently wide result type to avoid overflow](/proposals/p1083.md#use-a-sufficiently-wide-result-type-to-avoid-overflow)
- [Guarantee that all overflow errors are trapped](/proposals/p1083.md#guarantee-that-all-overflow-errors-are-trapped)
- [Guarantee that all integer arithmetic is two's complement](/proposals/p1083.md#guarantee-that-all-integer-arithmetic-is-twos-complement)
- [Treat overflow as an error but don't optimize on it](/proposals/p1083.md#treat-overflow-as-an-error-but-dont-optimize-on-it)
- [Don't let `Unsigned` arithmetic wrap](/proposals/p1083.md#dont-let-unsigned-arithmetic-wrap)
- [Provide separate wrapping types](/proposals/p1083.md#provide-separate-wrapping-types)
- [Do not provide an ordering or division for `uN`](/proposals/p1083.md#do-not-provide-an-ordering-or-division-for-un)
- [Give unary `-` lower precedence](/proposals/p1083.md#give-unary---lower-precedence)
- [Include a unary plus operator](/proposals/p1083.md#include-a-unary-plus-operator)
- [Floating-point modulo operator](/proposals/p1083.md#floating-point-modulo-operator)
- [Provide different division operators](/proposals/p1083.md#provide-different-division-operators)
- [Use different division and modulo semantics](/proposals/p1083.md#use-different-division-and-modulo-semantics)
- [Use different precedence groups for division and multiplication](/proposals/p1083.md#use-different-precedence-groups-for-division-and-multiplication)
- [Use the same precedence group for modulo and multiplication](/proposals/p1083.md#use-the-same-precedence-group-for-modulo-and-multiplication)
- [Use a different spelling for modulo](/proposals/p1083.md#use-a-different-spelling-for-modulo)
- [Use a different operator for string concatenation](/proposals/p1083.md#use-a-different-operator-for-string-concatenation)

## References

- Proposal
[#1083: arithmetic](https://github.com/carbon-language/carbon-lang/pull/1083).
Loading