Skip to content

Commit

Permalink
Show deprecation on arithmetic word operations (#2339)
Browse files Browse the repository at this point in the history
next step in the plan of
#1824 (comment)

should be merged after  #2326

Add a migration guide to the users guide.
  • Loading branch information
nomeata authored Feb 12, 2021
1 parent 833386c commit 521e23f
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 83 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
The motivation for this change is to eventually deprecate and remove the
`WordN` types.

Therefore, the wrapping arithmetic operations on `WordN` are deprecated and
their use will print a warning. See the user’s guide, section “Word types”,
for a migration guide.

* For values `x` of type `Blob`, an iterator over the elements of the blob
`x.vals()` is introduced. It works like `x.bytes()`, but returns the elements
as type `Nat8`.
Expand Down
31 changes: 31 additions & 0 deletions doc/modules/language-guide/pages/language-manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,37 @@ other arithmetic types, and their literals need type annotation, e.g.
`(-42 : Word16)`. For negative literals the two's-complement
representation is applied.

[IMPORTANT]
====
The `WordN` types will be deprecated in favor of the `NatN` and `IntN` types. This happens in two phases:
1. First, the wrapping arithmetic operations on word types (`+`, `-`, `*` or `**`) are deprecated, and the explicitly wrapping variants should be used (`+%`, +`-%`, `*%` and `**%`).
2. In the next phase, the `Word` types themselves are removed.
Developers can choose to follow these phases, and be guided by the deprecation
warnings, or can migrate away from using the `WordN` types now. In that case:
* Choose `NatN` or `IntN` as needed (typically `NatN`).
* For each use of `+`, `-`, `*` or `**`, decide whether you want to preserve
the wrapping behaviour, in which case you use `+%`, +`-%`, `*%` or `**%`, or
if you actually want the trapping behaviour of arithmetic on `NatN`/`IntN`.
* For each use of `Word8.fromInt`, decide whether you want wrapping or
trapping behaviour. Use `fromNat`/`fromInt` or `fromIntWrap` accordingly.
* Instead of `Char.fromWord32` and `Char.toWord32`, use `Char.fromNat32` and `Char.toNat32`.
* For each use of `>>` or `+>>`, decide if the right-shift on `Nat` resp.
`Int` gives you the desired semantics. If not, convert to the other type
before shifting.
* Use `blob.vals()` instead of `blob.bytes()`, to get a `Nat8`-iterator.
====


[[type-Blob]]
=== Type `Blob`

Expand Down
1 change: 1 addition & 0 deletions src/lang_utils/error_codes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,5 @@ let error_codes : (string * string option) list =
"M0149", Some([%blob "error_codes/M0149.adoc"]); (* Expected mutable 'var' field, found immutable field *)
"M0150", Some([%blob "error_codes/M0150.adoc"]); (* Expected immutable field, found mutable 'var' field *)
"M0151", Some([%blob "error_codes/M0151.adoc"]); (* missing field in object literal *)
"M0152", Some([%blob "error_codes/M0152.adoc"]); (* Word field deprecation *)
]
4 changes: 0 additions & 4 deletions src/lang_utils/error_codes/M0151.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@ This error means that a object literal is missing some fields, maybe because of

Erroneous code examples:

```
{ first_name = "Fred" } : { firstName : Text }
{ firstName = "Fred" } : { firstName : Text; lastName : Text }
```

If you encounter this error, you need to add the missing field name to the
object literal.

```
{ firstName = "Fred" } : { firstName : Text }
{ firstName = "Fred"; lastName = "Flintstone" } : { firstName : Text; lastName : Text }
```
19 changes: 19 additions & 0 deletions src/lang_utils/error_codes/M0152.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
= M0152

You are using a possibly wrapping operator (`+`, `-`, `*` or `**`) on a word
type (e.g. `Word8`). The word type will be deprecated in the future, and
replaced with `Nat8`, `Int8` etc. The arithmetic operations have different
semantics on these types, as they trap on overflow, instead of wrapping around.
to prepare the transition, you are advised to use the wrapping operators (`+%`,
`-%`, `*%` and `**%`) now. This way, when you eventually switch to `Nat8` or `Int8`,
these operators work as expected.

See the user's guide, section on `Word` type, for more information on this transition.

Erroneous code example:

func add(w1 : Word8, w2 : Word8) : Word8 { w1 + w2 };

Typical fix:

func add(w1 : Word8, w2 : Word8) : Word8 { w1 +% w2 };
17 changes: 17 additions & 0 deletions src/mo_frontend/typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ let coverage_cases category env cases t at =
let coverage_pat warnOrError env pat t =
coverage' warnOrError "pattern" env Coverage.check_pat pat t pat.at

(* Deprecation *)

let check_deprecation_binop env at t op =
let open Type in
let open Operator in
let word_op_warn s =
warn env at "M0152" "the arithmetic operation %s on %s is deprecated, use %s%% instead"
s (T.string_of_typ_expand t) s
in
match t, op with
| Prim (Word8|Word16|Word32|Word64), AddOp -> word_op_warn "+"
| Prim (Word8|Word16|Word32|Word64), SubOp -> word_op_warn "-"
| Prim (Word8|Word16|Word32|Word64), MulOp -> word_op_warn "*"
| Prim (Word8|Word16|Word32|Word64), PowOp -> word_op_warn "**"
| _, _ -> ()

(* Types *)

Expand Down Expand Up @@ -815,6 +830,7 @@ and infer_exp'' env exp : T.typ =
error_bin_op env exp.at t1 t2;
ot := t
end;
check_deprecation_binop env exp.at t op;
t
| RelE (ot, exp1, op, exp2) ->
let t1 = T.normalize (infer_exp env exp1) in
Expand Down Expand Up @@ -1228,6 +1244,7 @@ and check_exp' env0 t exp : T.typ =
ot := t;
check_exp env t exp1;
check_exp env t exp2;
check_deprecation_binop env exp.at t op;
t
| TupE exps, T.Tup ts when List.length exps = List.length ts ->
List.iter2 (check_exp env) ts exps;
Expand Down
5 changes: 0 additions & 5 deletions test/perf/assetstorage/Char.mo
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ module {
// Not exposed pending multi-char implementation.
private let toLower : (c : Char) -> Char = Prim.charToLower;

/// Returns `true` when `c` is a decimal digit between `0` and `9`, otherwise `false`.
public func isDigit(c : Char) : Bool {
Prim.charToWord32(c) - Prim.charToWord32('0') <= (9 : Word32)
};

/// Returns the Unicode _White_Space_ property of `c`.
public let isWhitespace : (c : Char) -> Bool = Prim.charIsWhitespace;

Expand Down
8 changes: 4 additions & 4 deletions test/perf/assetstorage/Hash.mo
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ module {
public func hashWord8(key : [Hash]) : Hash {
var hash = Prim.natToWord32(0);
for (wordOfKey in key.vals()) {
hash := hash + wordOfKey;
hash := hash + hash << 10;
hash := hash +% wordOfKey;
hash := hash +% hash << 10;
hash := hash ^ (hash >> 6);
};
hash := hash + hash << 3;
hash := hash +% hash << 3;
hash := hash ^ (hash >> 11);
hash := hash + hash << 15;
hash := hash +% hash << 15;
return hash;
};

Expand Down
2 changes: 1 addition & 1 deletion test/perf/qr/char.mo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import Prim "mo:prim";
module {
public let isDigit : Char -> Bool = func(char) {
Prim.charToWord32(char) - Prim.charToWord32('0') <= (9 : Word32)
Prim.charToWord32(char) -% Prim.charToWord32('0') <= (9 : Word32)
};

}
8 changes: 4 additions & 4 deletions test/perf/qr/hash.mo
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ module {
public let hashWord8s : [Hash] -> Hash = func(key) {
var hash = Prim.natToWord32(0);
for (wordOfKey in key.vals()) {
hash := hash + wordOfKey;
hash := hash + hash << 10;
hash := hash +% wordOfKey;
hash := hash +% hash << 10;
hash := hash ^ (hash >> 6);
};
hash := hash + hash << 3;
hash := hash +% hash << 3;
hash := hash ^ (hash >> 11);
hash := hash + hash << 15;
hash := hash +% hash << 15;
return hash;
};
Expand Down
2 changes: 1 addition & 1 deletion test/perf/qr/numeric.mo
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ module {
if (Char.isDigit(char)) {
Option.map<Nat, Nat>(func (a) {
let b = Prim.word32ToNat(
Prim.charToWord32(char) - Prim.charToWord32('0')
Prim.charToWord32(char) -% Prim.charToWord32('0')
);
10 * a + b
}, accum)
Expand Down
64 changes: 32 additions & 32 deletions test/run/numeric-ops.mo
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,18 @@ func testWord8(a : Word8, b : Word8) : [Word8] {
let pos2 = (+ a) : Word8;
let neg1 = - a;
let neg2 = (- a) : Word8;
let sum1 = a + b;
let sum2 = (a + b) : Word8;
let diff1 = a - b;
let diff2 = (a - b) : Word8;
let prod1 = a * b;
let prod2 = (a * b) : Word8;
let sum1 = a +% b;
let sum2 = (a +% b) : Word8;
let diff1 = a -% b;
let diff2 = (a -% b) : Word8;
let prod1 = a *% b;
let prod2 = (a *% b) : Word8;
let rat1 = a / b;
let rat2 = (a / b) : Word8;
let mod1 = a % b;
let mod2 = (a % b) : Word8;
let pow1 = a ** b;
let pow2 = (a ** b) : Word8;
let pow1 = a **% b;
let pow2 = (a **% b) : Word8;
[pos1, pos2, neg1, neg2, sum1, sum2, diff1, diff2, prod1, prod2, rat1, rat2, mod1, mod2, pow1, pow2]
};

Expand All @@ -179,18 +179,18 @@ func testWord16(a : Word16, b : Word16) : [Word16] {
let pos2 = (+ a) : Word16;
let neg1 = - a;
let neg2 = (- a) : Word16;
let sum1 = a + b;
let sum2 = (a + b) : Word16;
let diff1 = a - b;
let diff2 = (a - b) : Word16;
let prod1 = a * b;
let prod2 = (a * b) : Word16;
let sum1 = a +% b;
let sum2 = (a +% b) : Word16;
let diff1 = a -% b;
let diff2 = (a -% b) : Word16;
let prod1 = a *% b;
let prod2 = (a *% b) : Word16;
let rat1 = a / b;
let rat2 = (a / b) : Word16;
let mod1 = a % b;
let mod2 = (a % b) : Word16;
let pow1 = a ** b;
let pow2 = (a ** b) : Word16;
let pow1 = a **% b;
let pow2 = (a **% b) : Word16;
[pos1, pos2, neg1, neg2, sum1, sum2, diff1, diff2, prod1, prod2, rat1, rat2, mod1, mod2, pow1, pow2]
};

Expand All @@ -203,18 +203,18 @@ func testWord32(a : Word32, b : Word32) : [Word32] {
let pos2 = (+ a) : Word32;
let neg1 = - a;
let neg2 = (- a) : Word32;
let sum1 = a + b;
let sum2 = (a + b) : Word32;
let diff1 = a - b;
let diff2 = (a - b) : Word32;
let prod1 = a * b;
let prod2 = (a * b) : Word32;
let sum1 = a +% b;
let sum2 = (a +% b) : Word32;
let diff1 = a -% b;
let diff2 = (a -% b) : Word32;
let prod1 = a *% b;
let prod2 = (a *% b) : Word32;
let rat1 = a / b;
let rat2 = (a / b) : Word32;
let mod1 = a % b;
let mod2 = (a % b) : Word32;
let pow1 = a ** b;
let pow2 = (a ** b) : Word32;
let pow1 = a **% b;
let pow2 = (a **% b) : Word32;
[pos1, pos2, neg1, neg2, sum1, sum2, diff1, diff2, prod1, prod2, rat1, rat2, mod1, mod2, pow1, pow2]
};

Expand All @@ -227,18 +227,18 @@ func testWord64(a : Word64, b : Word64) : [Word64] {
let pos2 = (+ a) : Word64;
let neg1 = - a;
let neg2 = (- a) : Word64;
let sum1 = a + b;
let sum2 = (a + b) : Word64;
let diff1 = a - b;
let diff2 = (a - b) : Word64;
let prod1 = a * b;
let prod2 = (a * b) : Word64;
let sum1 = a +% b;
let sum2 = (a +% b) : Word64;
let diff1 = a -% b;
let diff2 = (a -% b) : Word64;
let prod1 = a *% b;
let prod2 = (a *% b) : Word64;
let rat1 = a / b;
let rat2 = (a / b) : Word64;
let mod1 = a % b;
let mod2 = (a % b) : Word64;
let pow1 = a ** b;
let pow2 = (a ** b) : Word64;
let pow1 = a **% b;
let pow2 = (a **% b) : Word64;
[pos1, pos2, neg1, neg2, sum1, sum2, diff1, diff2, prod1, prod2, rat1, rat2, mod1, mod2, pow1, pow2]
};

Expand Down
32 changes: 32 additions & 0 deletions test/run/ok/word-ops-deprecation.tc.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
word-ops-deprecation.mo:2.34-2.39: warning [M0152], the arithmetic operation + on Word8 is deprecated, use +% instead
word-ops-deprecation.mo:3.34-3.39: warning [M0152], the arithmetic operation - on Word8 is deprecated, use -% instead
word-ops-deprecation.mo:4.34-4.39: warning [M0152], the arithmetic operation * on Word8 is deprecated, use *% instead
word-ops-deprecation.mo:5.34-5.40: warning [M0152], the arithmetic operation ** on Word8 is deprecated, use **% instead
word-ops-deprecation.mo:6.35-6.40: warning [M0152], the arithmetic operation + on Word16 is deprecated, use +% instead
word-ops-deprecation.mo:7.35-7.40: warning [M0152], the arithmetic operation - on Word16 is deprecated, use -% instead
word-ops-deprecation.mo:8.35-8.40: warning [M0152], the arithmetic operation * on Word16 is deprecated, use *% instead
word-ops-deprecation.mo:9.35-9.41: warning [M0152], the arithmetic operation ** on Word16 is deprecated, use **% instead
word-ops-deprecation.mo:10.35-10.40: warning [M0152], the arithmetic operation + on Word32 is deprecated, use +% instead
word-ops-deprecation.mo:11.35-11.40: warning [M0152], the arithmetic operation - on Word32 is deprecated, use -% instead
word-ops-deprecation.mo:12.35-12.40: warning [M0152], the arithmetic operation * on Word32 is deprecated, use *% instead
word-ops-deprecation.mo:13.35-13.41: warning [M0152], the arithmetic operation ** on Word32 is deprecated, use **% instead
word-ops-deprecation.mo:14.35-14.40: warning [M0152], the arithmetic operation + on Word64 is deprecated, use +% instead
word-ops-deprecation.mo:15.35-15.40: warning [M0152], the arithmetic operation - on Word64 is deprecated, use -% instead
word-ops-deprecation.mo:16.35-16.40: warning [M0152], the arithmetic operation * on Word64 is deprecated, use *% instead
word-ops-deprecation.mo:17.35-17.41: warning [M0152], the arithmetic operation ** on Word64 is deprecated, use **% instead
word-ops-deprecation.mo:20.36-20.41: warning [M0152], the arithmetic operation + on Word8 is deprecated, use +% instead
word-ops-deprecation.mo:21.36-21.41: warning [M0152], the arithmetic operation - on Word8 is deprecated, use -% instead
word-ops-deprecation.mo:22.36-22.41: warning [M0152], the arithmetic operation * on Word8 is deprecated, use *% instead
word-ops-deprecation.mo:23.36-23.42: warning [M0152], the arithmetic operation ** on Word8 is deprecated, use **% instead
word-ops-deprecation.mo:24.38-24.43: warning [M0152], the arithmetic operation + on Word16 is deprecated, use +% instead
word-ops-deprecation.mo:25.38-25.43: warning [M0152], the arithmetic operation - on Word16 is deprecated, use -% instead
word-ops-deprecation.mo:26.38-26.43: warning [M0152], the arithmetic operation * on Word16 is deprecated, use *% instead
word-ops-deprecation.mo:27.38-27.44: warning [M0152], the arithmetic operation ** on Word16 is deprecated, use **% instead
word-ops-deprecation.mo:28.38-28.43: warning [M0152], the arithmetic operation + on Word32 is deprecated, use +% instead
word-ops-deprecation.mo:29.38-29.43: warning [M0152], the arithmetic operation - on Word32 is deprecated, use -% instead
word-ops-deprecation.mo:30.38-30.43: warning [M0152], the arithmetic operation * on Word32 is deprecated, use *% instead
word-ops-deprecation.mo:31.38-31.44: warning [M0152], the arithmetic operation ** on Word32 is deprecated, use **% instead
word-ops-deprecation.mo:32.38-32.43: warning [M0152], the arithmetic operation + on Word64 is deprecated, use +% instead
word-ops-deprecation.mo:33.38-33.43: warning [M0152], the arithmetic operation - on Word64 is deprecated, use -% instead
word-ops-deprecation.mo:34.38-34.43: warning [M0152], the arithmetic operation * on Word64 is deprecated, use *% instead
word-ops-deprecation.mo:35.38-35.44: warning [M0152], the arithmetic operation ** on Word64 is deprecated, use **% instead
Loading

0 comments on commit 521e23f

Please sign in to comment.