Skip to content

Commit b09ea66

Browse files
chore(compiler): Add fromNumber(<literal>) warning for short types (#2024)
Co-authored-by: Oscar Spencer <oscar@grain-lang.org>
1 parent ca75e38 commit b09ea66

File tree

4 files changed

+192
-88
lines changed

4 files changed

+192
-88
lines changed

compiler/src/typed/typed_well_formedness.re

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
324324
};
325325
| _ => ()
326326
}
327-
// Check: Warn if using Int32.fromNumber(<literal>)
327+
// Check: Warn if using XXXX.fromNumber(<literal>)
328328
| TExpApp(
329329
{
330330
exp_desc:
@@ -335,44 +335,48 @@ module WellFormednessArg: TypedtreeIter.IteratorArgument = {
335335
),
336336
},
337337
_,
338-
[
339-
(
340-
Unlabeled,
341-
{
342-
exp_desc:
343-
TExpConstant(
344-
Const_number(
345-
(Const_number_int(_) | Const_number_float(_)) as n,
346-
),
347-
),
348-
},
349-
),
350-
],
338+
[(_, {exp_desc: TExpConstant(Const_number(n))})],
351339
)
352340
when
353-
modname == "Int32"
341+
modname == "Int8"
342+
|| modname == "Int16"
343+
|| modname == "Int32"
354344
|| modname == "Int64"
345+
|| modname == "Uint8"
346+
|| modname == "Uint16"
355347
|| modname == "Uint32"
356348
|| modname == "Uint64"
357349
|| modname == "Float32"
358-
|| modname == "Float64" =>
350+
|| modname == "Float64"
351+
|| modname == "Rational"
352+
|| modname == "BigInt" =>
359353
// NOTE: Due to type-checking, we shouldn't need to worry about ending up with a FloatXX value and a Const_number_float
360354
let n_str =
361355
switch (n) {
362356
| Const_number_int(nint) => Int64.to_string(nint)
363357
| Const_number_float(nfloat) => Float.to_string(nfloat)
364-
| _ => failwith("Impossible")
358+
| Const_number_rational({rational_num_rep, rational_den_rep}) =>
359+
Printf.sprintf("%s/%s", rational_num_rep, rational_den_rep)
360+
| Const_number_bigint({bigint_rep}) => bigint_rep
365361
};
366-
let warning =
362+
let mod_type =
367363
switch (modname) {
368-
| "Int32" => Grain_utils.Warnings.FromNumberLiteralI32(n_str)
369-
| "Int64" => Grain_utils.Warnings.FromNumberLiteralI64(n_str)
370-
| "Uint32" => Grain_utils.Warnings.FromNumberLiteralU32(n_str)
371-
| "Uint64" => Grain_utils.Warnings.FromNumberLiteralU64(n_str)
372-
| "Float32" => Grain_utils.Warnings.FromNumberLiteralF32(n_str)
373-
| "Float64" => Grain_utils.Warnings.FromNumberLiteralF64(n_str)
364+
| "Int8" => Grain_utils.Warnings.Int8
365+
| "Int16" => Grain_utils.Warnings.Int16
366+
| "Int32" => Grain_utils.Warnings.Int32
367+
| "Int64" => Grain_utils.Warnings.Int64
368+
| "Uint8" => Grain_utils.Warnings.Uint8
369+
| "Uint16" => Grain_utils.Warnings.Uint16
370+
| "Uint32" => Grain_utils.Warnings.Uint32
371+
| "Uint64" => Grain_utils.Warnings.Uint64
372+
| "Float32" => Grain_utils.Warnings.Float32
373+
| "Float64" => Grain_utils.Warnings.Float64
374+
| "Rational" => Grain_utils.Warnings.Rational
375+
| "BigInt" => Grain_utils.Warnings.BigInt
374376
| _ => failwith("Impossible")
375377
};
378+
let warning =
379+
Grain_utils.Warnings.FromNumberLiteral(mod_type, modname, n_str);
376380
if (Grain_utils.Warnings.is_active(warning)) {
377381
Grain_parsing.Location.prerr_warning(exp_loc, warning);
378382
};

compiler/src/utils/warnings.re

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ type loc = {
66
loc_ghost: bool,
77
};
88

9+
type number_type =
10+
| Int8
11+
| Int16
12+
| Int32
13+
| Int64
14+
| Uint8
15+
| Uint16
16+
| Uint32
17+
| Uint64
18+
| Float32
19+
| Float64
20+
| Rational
21+
| BigInt;
22+
923
type t =
1024
| LetRecNonFunction(string)
1125
| AmbiguousName(list(string), list(string), bool)
@@ -24,17 +38,12 @@ type t =
2438
| ShadowConstructor(string)
2539
| NoCmiFile(string, option(string))
2640
| FuncWasmUnsafe(string, string, string)
27-
| FromNumberLiteralI32(string)
28-
| FromNumberLiteralI64(string)
29-
| FromNumberLiteralU32(string)
30-
| FromNumberLiteralU64(string)
31-
| FromNumberLiteralF32(string)
32-
| FromNumberLiteralF64(string)
41+
| FromNumberLiteral(number_type, string, string)
3342
| UselessRecordSpread
3443
| PrintUnsafe(string)
3544
| ToStringUnsafe(string);
3645

37-
let last_warning_number = 26;
46+
let last_warning_number = 21;
3847

3948
let number =
4049
fun
@@ -55,14 +64,9 @@ let number =
5564
| NonClosedRecordPattern(_) => 15
5665
| UnusedExtension => 16
5766
| FuncWasmUnsafe(_, _, _) => 17
58-
| FromNumberLiteralI32(_) => 18
59-
| FromNumberLiteralI64(_) => 19
60-
| FromNumberLiteralU32(_) => 20
61-
| FromNumberLiteralU64(_) => 21
62-
| FromNumberLiteralF32(_) => 22
63-
| FromNumberLiteralF64(_) => 23
64-
| UselessRecordSpread => 24
65-
| PrintUnsafe(_) => 25
67+
| FromNumberLiteral(_, _, _) => 18
68+
| UselessRecordSpread => 19
69+
| PrintUnsafe(_) => 20
6670
| ToStringUnsafe(_) => last_warning_number;
6771

6872
let message =
@@ -131,36 +135,32 @@ let message =
131135
++ " from the `"
132136
++ m
133137
++ "` module the instead."
134-
| FromNumberLiteralI32(n) =>
135-
Printf.sprintf(
136-
"it looks like you are calling Int32.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sl`) instead.",
137-
n,
138-
)
139-
| FromNumberLiteralI64(n) =>
140-
Printf.sprintf(
141-
"it looks like you are calling Int64.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sL`) instead.",
142-
n,
143-
)
144-
| FromNumberLiteralU32(n) =>
145-
Printf.sprintf(
146-
"it looks like you are calling Uint32.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sul`) instead.",
147-
n,
148-
)
149-
| FromNumberLiteralU64(n) =>
150-
Printf.sprintf(
151-
"it looks like you are calling Uint64.fromNumber() with a constant number. Try using the literal syntax (e.g. `%suL`) instead.",
152-
n,
153-
)
154-
| FromNumberLiteralF32(n) =>
155-
Printf.sprintf(
156-
"it looks like you are calling Float32.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sf`) instead.",
157-
String.contains(n, '.') ? n : n ++ ".",
158-
)
159-
| FromNumberLiteralF64(n) =>
160-
Printf.sprintf(
161-
"it looks like you are calling Float64.fromNumber() with a constant number. Try using the literal syntax (e.g. `%sd`) instead.",
162-
String.contains(n, '.') ? n : n ++ ".",
163-
)
138+
| FromNumberLiteral(mod_type, mod_name, n) => {
139+
let literal =
140+
switch (mod_type) {
141+
| Int8 => Printf.sprintf("%ss", n)
142+
| Int16 => Printf.sprintf("%sS", n)
143+
| Int32 => Printf.sprintf("%sl", n)
144+
| Int64 => Printf.sprintf("%sL", n)
145+
| Uint8 => Printf.sprintf("%sus", n)
146+
| Uint16 => Printf.sprintf("%suS", n)
147+
| Uint32 => Printf.sprintf("%sul", n)
148+
| Uint64 => Printf.sprintf("%suL", n)
149+
| Float32 =>
150+
Printf.sprintf("%sf", String.contains(n, '.') ? n : n ++ ".")
151+
152+
| Float64 =>
153+
Printf.sprintf("%sd", String.contains(n, '.') ? n : n ++ ".")
154+
| Rational =>
155+
Printf.sprintf("%sr", String.contains(n, '/') ? n : n ++ "/1")
156+
| BigInt => Printf.sprintf("%st", n)
157+
};
158+
Printf.sprintf(
159+
"it looks like you are calling %s.fromNumber() with a constant number. Try using the literal syntax (e.g. `%s`) instead.",
160+
mod_name,
161+
literal,
162+
);
163+
}
164164
| UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden."
165165
| PrintUnsafe(typ) =>
166166
"it looks like you are using `print` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.print`"
@@ -212,12 +212,7 @@ let defaults = [
212212
ShadowConstructor(""),
213213
NoCmiFile("", None),
214214
FuncWasmUnsafe("", "", ""),
215-
FromNumberLiteralI32(""),
216-
FromNumberLiteralI64(""),
217-
FromNumberLiteralU32(""),
218-
FromNumberLiteralU64(""),
219-
FromNumberLiteralF32(""),
220-
FromNumberLiteralF64(""),
215+
FromNumberLiteral(Int8, "", ""),
221216
UselessRecordSpread,
222217
PrintUnsafe(""),
223218
ToStringUnsafe(""),

compiler/src/utils/warnings.rei

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ type loc = {
2020
loc_ghost: bool,
2121
};
2222

23+
type number_type =
24+
| Int8
25+
| Int16
26+
| Int32
27+
| Int64
28+
| Uint8
29+
| Uint16
30+
| Uint32
31+
| Uint64
32+
| Float32
33+
| Float64
34+
| Rational
35+
| BigInt;
36+
2337
type t =
2438
| LetRecNonFunction(string)
2539
| AmbiguousName(list(string), list(string), bool)
@@ -38,12 +52,7 @@ type t =
3852
| ShadowConstructor(string)
3953
| NoCmiFile(string, option(string))
4054
| FuncWasmUnsafe(string, string, string)
41-
| FromNumberLiteralI32(string)
42-
| FromNumberLiteralI64(string)
43-
| FromNumberLiteralU32(string)
44-
| FromNumberLiteralU64(string)
45-
| FromNumberLiteralF32(string)
46-
| FromNumberLiteralF64(string)
55+
| FromNumberLiteral(number_type, string, string)
4756
| UselessRecordSpread
4857
| PrintUnsafe(string)
4958
| ToStringUnsafe(string);

compiler/test/suites/numbers.re

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,34 +199,130 @@ describe("numbers", ({test, testSkip}) => {
199199
);
200200

201201
// well-formedness warnings
202-
test("float32_fromNumber_warn1", ({expect}) => {
203-
expect.string(Warnings.message(FromNumberLiteralF32("5"))).toMatch(
202+
test("short_fromNumber_warn1", ({expect}) => {
203+
expect.string(Warnings.message(FromNumberLiteral(Uint8, "Uint8", "2"))).
204+
toMatch(
205+
"2us",
206+
)
207+
});
208+
test("short_fromNumber_warn2", ({expect}) => {
209+
expect.string(
210+
Warnings.message(FromNumberLiteral(Uint16, "Uint16", "2")),
211+
).
212+
toMatch(
213+
"2uS",
214+
)
215+
});
216+
test("short_fromNumber_warn3", ({expect}) => {
217+
expect.string(
218+
Warnings.message(FromNumberLiteral(Uint32, "Uint32", "2")),
219+
).
220+
toMatch(
221+
"2ul",
222+
)
223+
});
224+
test("short_fromNumber_warn4", ({expect}) => {
225+
expect.string(
226+
Warnings.message(FromNumberLiteral(Uint64, "Uint64", "2")),
227+
).
228+
toMatch(
229+
"2uL",
230+
)
231+
});
232+
test("short_fromNumber_warn5", ({expect}) => {
233+
expect.string(Warnings.message(FromNumberLiteral(Int8, "Int8", "2"))).
234+
toMatch(
235+
"2s",
236+
)
237+
});
238+
test("short_fromNumber_warn6", ({expect}) => {
239+
expect.string(Warnings.message(FromNumberLiteral(Int16, "Int16", "2"))).
240+
toMatch(
241+
"2S",
242+
)
243+
});
244+
test("short_fromNumber_warn7", ({expect}) => {
245+
expect.string(Warnings.message(FromNumberLiteral(Int32, "Int32", "2"))).
246+
toMatch(
247+
"2l",
248+
)
249+
});
250+
test("short_fromNumber_warn8", ({expect}) => {
251+
expect.string(Warnings.message(FromNumberLiteral(Int64, "Int64", "2"))).
252+
toMatch(
253+
"2L",
254+
)
255+
});
256+
test("float32_fromNumber_warn9", ({expect}) => {
257+
expect.string(
258+
Warnings.message(FromNumberLiteral(Float32, "Float32", "5")),
259+
).
260+
toMatch(
204261
"5.f",
205262
)
206263
});
207264
test("float32_fromNumber_warn2", ({expect}) => {
208-
expect.string(Warnings.message(FromNumberLiteralF32("5."))).toMatch(
265+
expect.string(
266+
Warnings.message(FromNumberLiteral(Float32, "Float32", "5.")),
267+
).
268+
toMatch(
209269
"5.f",
210270
)
211271
});
212272
test("float32_fromNumber_warn3", ({expect}) => {
213-
expect.string(Warnings.message(FromNumberLiteralF32("5.5"))).toMatch(
273+
expect.string(
274+
Warnings.message(FromNumberLiteral(Float32, "Float32", "5.5")),
275+
).
276+
toMatch(
214277
"5.5f",
215278
)
216279
});
217280
test("float64_fromNumber_warn1", ({expect}) => {
218-
expect.string(Warnings.message(FromNumberLiteralF64("5"))).toMatch(
281+
expect.string(
282+
Warnings.message(FromNumberLiteral(Float64, "Float64", "5")),
283+
).
284+
toMatch(
219285
"5.d",
220286
)
221287
});
222288
test("float64_fromNumber_warn2", ({expect}) => {
223-
expect.string(Warnings.message(FromNumberLiteralF64("5."))).toMatch(
289+
expect.string(
290+
Warnings.message(FromNumberLiteral(Float64, "Float64", "5.")),
291+
).
292+
toMatch(
224293
"5.d",
225294
)
226295
});
227296
test("float64_fromNumber_warn3", ({expect}) => {
228-
expect.string(Warnings.message(FromNumberLiteralF64("5.5"))).toMatch(
297+
expect.string(
298+
Warnings.message(FromNumberLiteral(Float64, "Float64", "5.5")),
299+
).
300+
toMatch(
229301
"5.5d",
230302
)
231303
});
304+
test("rational_fromNumber_warn1", ({expect}) => {
305+
expect.string(
306+
Warnings.message(FromNumberLiteral(Rational, "Rational", "2")),
307+
).
308+
toMatch(
309+
"2/1r",
310+
)
311+
});
312+
test("rational_fromNumber_warn2", ({expect}) => {
313+
expect.string(
314+
Warnings.message(FromNumberLiteral(Rational, "Rational", "2/4")),
315+
).
316+
toMatch(
317+
"2/4r",
318+
)
319+
});
320+
test("bigint_fromNumber_warn1", ({expect}) => {
321+
expect.string(
322+
Warnings.message(FromNumberLiteral(BigInt, "Bigint", "2")),
323+
).
324+
toMatch(
325+
"2t",
326+
)
327+
});
232328
});

0 commit comments

Comments
 (0)