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

chore(compiler): Add warning for print and toString with unsafe values #2018

Merged
merged 7 commits into from Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 53 additions & 5 deletions compiler/src/typed/typed_well_formedness.re
Expand Up @@ -42,10 +42,10 @@ let rec resolve_unsafe_type = ({exp_type}) => {
switch (t.desc) {
| TTyConstr(path, _, _) =>
switch (path) {
| t when t == Builtin_types.path_wasmi32 => "WasmI32"
| t when t == Builtin_types.path_wasmi64 => "WasmI64"
| t when t == Builtin_types.path_wasmf32 => "WasmF32"
| t when t == Builtin_types.path_wasmf64 => "WasmI64"
| t when t == Builtin_types.path_wasmi32 => "I32"
| t when t == Builtin_types.path_wasmi64 => "I64"
| t when t == Builtin_types.path_wasmf32 => "F32"
| t when t == Builtin_types.path_wasmf64 => "I64"
| _ => failwith("Impossible: type cannot be a wasm unsafe value")
}
| TTyLink(t) => type_is_wasm_unsafe(t)
Expand Down Expand Up @@ -263,7 +263,8 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
if (List.exists(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
let typeName =
switch (args) {
| [(_, arg), _] => resolve_unsafe_type(arg)
| [(_, arg), _] when exp_is_wasm_unsafe(arg) =>
"Wasm" ++ resolve_unsafe_type(arg)
| _ => "(WasmI32 | WasmI64 | WasmF32 | WasmF64)"
};
let warning =
Expand All @@ -276,6 +277,53 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
Grain_parsing.Location.prerr_warning(exp_loc, warning);
};
}
// Check: Warn if using Pervasives print on WasmXX types
| TExpApp(
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
{
exp_desc:
TExpIdent(
Path.PExternal(Path.PIdent({name: "Pervasives"}), "print"),
phated marked this conversation as resolved.
Show resolved Hide resolved
_,
_,
),
},
_,
args,
) =>
switch (List.find_opt(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
| Some((_, arg)) =>
let typeName = resolve_unsafe_type(arg);
let warning = Grain_utils.Warnings.PrintUnsafe(typeName);
if (Grain_utils.Warnings.is_active(warning)) {
Grain_parsing.Location.prerr_warning(exp_loc, warning);
};
| _ => ()
}
// Check: Warn if using Pervasives toString on WasmXX types
| TExpApp(
{
exp_desc:
TExpIdent(
Path.PExternal(
Path.PIdent({name: "Pervasives"}),
"toString",
),
_,
_,
),
},
_,
args,
) =>
switch (List.find_opt(((_, arg)) => exp_is_wasm_unsafe(arg), args)) {
| Some((_, arg)) =>
let typeName = resolve_unsafe_type(arg);
let warning = Grain_utils.Warnings.ToStringUnsafe(typeName);
if (Grain_utils.Warnings.is_active(warning)) {
Grain_parsing.Location.prerr_warning(exp_loc, warning);
};
| _ => ()
}
// Check: Warn if using Int32.fromNumber(<literal>)
| TExpApp(
{
Expand Down
22 changes: 18 additions & 4 deletions compiler/src/utils/warnings.re
Expand Up @@ -30,9 +30,11 @@ type t =
| FromNumberLiteralU64(string)
| FromNumberLiteralF32(string)
| FromNumberLiteralF64(string)
| UselessRecordSpread;
| UselessRecordSpread
| PrintUnsafe(string)
| ToStringUnsafe(string);

let last_warning_number = 24;
let last_warning_number = 26;

let number =
fun
Expand All @@ -59,7 +61,9 @@ let number =
| FromNumberLiteralU64(_) => 21
| FromNumberLiteralF32(_) => 22
| FromNumberLiteralF64(_) => 23
| UselessRecordSpread => last_warning_number;
| UselessRecordSpread => 24
| PrintUnsafe(_) => 25
| ToStringUnsafe(_) => last_warning_number;

let message =
fun
Expand Down Expand Up @@ -157,7 +161,15 @@ let message =
"it looks like you are calling Float64.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sd`) instead.",
String.contains(n, '.') ? n : n ++ ".",
)
| UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden.";
| UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden."
| PrintUnsafe(typ) =>
"it looks like you are using `print` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.print`"
++ typ
++ " from the `runtime/debugPrint` module instead."
| ToStringUnsafe(typ) =>
"it looks like you are using `toString` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.toString`"
++ typ
++ " from the `runtime/debugPrint` module instead.";

let sub_locs =
fun
Expand Down Expand Up @@ -207,6 +219,8 @@ let defaults = [
FromNumberLiteralF32(""),
FromNumberLiteralF64(""),
UselessRecordSpread,
PrintUnsafe(""),
ToStringUnsafe(""),
];

let _ = List.iter(x => current^.active[number(x)] = true, defaults);
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/utils/warnings.rei
Expand Up @@ -44,7 +44,9 @@ type t =
| FromNumberLiteralU64(string)
| FromNumberLiteralF32(string)
| FromNumberLiteralF64(string)
| UselessRecordSpread;
| UselessRecordSpread
| PrintUnsafe(string)
| ToStringUnsafe(string);

let is_active: t => bool;
let is_error: t => bool;
Expand Down
Expand Up @@ -12,18 +12,18 @@ basic functionality › unsafe_wasm_globals
(import \"_genv\" \"runtimeHeapStart\" (global $runtimeHeapStart_0 i32))
(import \"_genv\" \"runtimeHeapNextPtr\" (global $runtimeHeapNextPtr_0 (mut i32)))
(import \"_genv\" \"metadataPtr\" (global $metadataPtr_0 i32))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F64_VAL\" (global $_F64_VAL_1147 (mut f64)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF64\" (global $printF64_1146 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F32_VAL\" (global $_F32_VAL_1145 (mut f32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF32\" (global $printF32_1144 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I64_VAL\" (global $_I64_VAL_1143 (mut i64)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI64\" (global $printI64_1142 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I32_VAL\" (global $_I32_VAL_1141 (mut i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI32\" (global $printI32_1140 (mut i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF64\" (func $printF64_1146 (param i32 f64) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF32\" (func $printF32_1144 (param i32 f32) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI64\" (func $printI64_1142 (param i32 i64) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI32\" (func $printI32_1140 (param i32 i32) (result i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F64_VAL\" (global $_F64_VAL_1159 (mut f64)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF64\" (global $printF64_1158 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_F32_VAL\" (global $_F32_VAL_1157 (mut f32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printF32\" (global $printF32_1156 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I64_VAL\" (global $_I64_VAL_1155 (mut i64)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI64\" (global $printI64_1154 (mut i32)))
(import \"GRAIN$MODULE$unsafeWasmGlobalsExports.gr\" \"GRAIN$EXPORT$_I32_VAL\" (global $_I32_VAL_1153 (mut i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"GRAIN$EXPORT$printI32\" (global $printI32_1152 (mut i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF64\" (func $printF64_1158 (param i32 f64) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printF32\" (func $printF32_1156 (param i32 f32) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI64\" (func $printI64_1154 (param i32 i64) (result i32)))
(import \"GRAIN$MODULE$runtime/debugPrint.gr\" \"printI32\" (func $printI32_1152 (param i32 i32) (result i32)))
(global $GRAIN$TABLE_SIZE i32 (i32.const 0))
(memory $0 0)
(elem $elem (global.get $relocBase_0))
Expand All @@ -40,26 +40,26 @@ basic functionality › unsafe_wasm_globals
(local $5 f64)
(block $compile_block.1
(drop
(call $printI32_1140
(global.get $printI32_1140)
(global.get $_I32_VAL_1141)
(call $printI32_1152
(global.get $printI32_1152)
(global.get $_I32_VAL_1153)
)
)
(drop
(call $printI64_1142
(global.get $printI64_1142)
(global.get $_I64_VAL_1143)
(call $printI64_1154
(global.get $printI64_1154)
(global.get $_I64_VAL_1155)
)
)
(drop
(call $printF32_1144
(global.get $printF32_1144)
(global.get $_F32_VAL_1145)
(call $printF32_1156
(global.get $printF32_1156)
(global.get $_F32_VAL_1157)
)
)
(return_call $printF64_1146
(global.get $printF64_1146)
(global.get $_F64_VAL_1147)
(return_call $printF64_1158
(global.get $printF64_1158)
(global.get $_F64_VAL_1159)
)
)
)
Expand Down
20 changes: 20 additions & 0 deletions stdlib/runtime/debugPrint.gr
Expand Up @@ -48,3 +48,23 @@ provide let printF32 = val => {
provide let printF64 = val => {
print(Utils.dtoa(val))
}

@unsafe
provide let toStringI32 = val => {
Utils.itoa32(val, 10n)
}

@unsafe
provide let toStringI64 = val => {
Utils.itoa64(val, 10n)
}

@unsafe
provide let toStringF32 = val => {
Utils.dtoa(WasmF64.promoteF32(val))
}

@unsafe
provide let toStringF64 = val => {
Utils.dtoa(val)
}
24 changes: 24 additions & 0 deletions stdlib/runtime/debugPrint.md
Expand Up @@ -36,3 +36,27 @@ printF32 : (val: WasmF32) => Void
printF64 : (val: WasmF64) => Void
```

### DebugPrint.**toStringI32**

```grain
toStringI32 : (val: WasmI32) => String
```

### DebugPrint.**toStringI64**

```grain
toStringI64 : (val: WasmI64) => String
```

### DebugPrint.**toStringF32**

```grain
toStringF32 : (val: WasmF32) => String
```

### DebugPrint.**toStringF64**

```grain
toStringF64 : (val: WasmF64) => String
```