diff --git a/compiler/src/typed/typed_well_formedness.re b/compiler/src/typed/typed_well_formedness.re index ac6956737e..6a74a72aba 100644 --- a/compiler/src/typed/typed_well_formedness.re +++ b/compiler/src/typed/typed_well_formedness.re @@ -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) @@ -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 = @@ -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( + { + exp_desc: + TExpIdent( + Path.PExternal(Path.PIdent({name: "Pervasives"}), "print"), + _, + _, + ), + }, + _, + 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() | TExpApp( { diff --git a/compiler/src/utils/warnings.re b/compiler/src/utils/warnings.re index 8a00138cd6..de98c6c2b9 100644 --- a/compiler/src/utils/warnings.re +++ b/compiler/src/utils/warnings.re @@ -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 @@ -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 @@ -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 @@ -207,6 +219,8 @@ let defaults = [ FromNumberLiteralF32(""), FromNumberLiteralF64(""), UselessRecordSpread, + PrintUnsafe(""), + ToStringUnsafe(""), ]; let _ = List.iter(x => current^.active[number(x)] = true, defaults); diff --git a/compiler/src/utils/warnings.rei b/compiler/src/utils/warnings.rei index 0cc64e6503..f763537ddf 100644 --- a/compiler/src/utils/warnings.rei +++ b/compiler/src/utils/warnings.rei @@ -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; diff --git a/compiler/test/__snapshots__/basic_functionality.1bf5759c.0.snapshot b/compiler/test/__snapshots__/basic_functionality.1bf5759c.0.snapshot index 1b7a917c6b..c5cc1222ef 100644 --- a/compiler/test/__snapshots__/basic_functionality.1bf5759c.0.snapshot +++ b/compiler/test/__snapshots__/basic_functionality.1bf5759c.0.snapshot @@ -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)) @@ -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) ) ) ) diff --git a/stdlib/runtime/debugPrint.gr b/stdlib/runtime/debugPrint.gr index b314e3ab91..194a7d1af2 100644 --- a/stdlib/runtime/debugPrint.gr +++ b/stdlib/runtime/debugPrint.gr @@ -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) +} diff --git a/stdlib/runtime/debugPrint.md b/stdlib/runtime/debugPrint.md index 334bc454c1..2c8db28bb5 100644 --- a/stdlib/runtime/debugPrint.md +++ b/stdlib/runtime/debugPrint.md @@ -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 +``` +