From 37ad9299b4f8c9f29b036cac26ac3ae39a6336eb Mon Sep 17 00:00:00 2001 From: PandaHUN777 <117030640+PandaHUN777@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:30:02 +0200 Subject: [PATCH] feat(typecheck): suggest List.concat for list addition --- src/types/mod.rs | 30 +++++++++++++++++++----------- tests/typecheck.rs | 8 +++++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index 0189c12..e82845a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6602,11 +6602,11 @@ impl Infer { let lt = self.infer_expr(lhs, env)?; let (lb, lu) = self .expect_num(<, lhs.span()) - .map_err(|e| self.string_concat_hint(op, <, e))?; + .map_err(|e| self.addition_hint(op, <, e))?; let rt = self.infer_expr(rhs, env)?; let (rb, ru) = self .expect_num(&rt, rhs.span()) - .map_err(|e| self.string_concat_hint(op, &rt, e))?; + .map_err(|e| self.addition_hint(op, &rt, e))?; let base_clash = |this: &Self| { mismatch( &this.num_ty(lb, this.apply_unit(&lu)), @@ -6635,15 +6635,23 @@ impl Infer { } } - /// Turn the generic "expected int, found string" from an `expect_num` failure - /// into a guiding hint when the user wrote `+` between strings — `+` is numeric - /// (§7.1), and `String.concat` is the concatenation path. Only augments the - /// `Add`-on-`string` case; every other numeric mismatch keeps its message. - fn string_concat_hint(&self, op: BinOp, operand: &Ty, mut err: TypeError) -> TypeError { - if op == BinOp::Add && matches!(self.apply(operand), Ty::Str) { - err.message = - "`+` is numeric and does not concatenate strings — use `String.concat a b`" - .to_string(); + /// Turn generic numeric type errors into guidance when `+` is used for + /// string or list concatenation. Every other numeric mismatch keeps its message. + fn addition_hint(&self, op: BinOp, operand: &Ty, mut err: TypeError) -> TypeError { + if op == BinOp::Add { + match self.apply(operand) { + Ty::Str => { + err.message = + "`+` is numeric and does not concatenate strings — use `String.concat a b`" + .to_string(); + } + Ty::Con(name, _) if name == "List" => { + err.message = + "`+` is numeric and does not concatenate lists — use `List.concat [a] [b]`" + .to_string(); + } + _ => {} + } } err } diff --git a/tests/typecheck.rs b/tests/typecheck.rs index 3f9cceb..d235af0 100644 --- a/tests/typecheck.rs +++ b/tests/typecheck.rs @@ -3978,12 +3978,18 @@ fn plus_on_strings_hints_at_string_concat() { assert_error_contains("let s = \"a\" + 1", "String.concat"); } +#[test] +fn plus_on_lists_hints_at_list_concat() { + assert_error_contains("let xs = [1, 2] + [3]", "List.concat [a] [b]"); +} + #[test] fn a_plain_numeric_mismatch_keeps_its_message() { // A non-string numeric mismatch must NOT gain a spurious concat hint. let msgs = errors("let bad = 1 + true"); assert!( - msgs.iter().all(|m| !m.contains("String.concat")), + msgs.iter() + .all(|m| !m.contains("String.concat") && !m.contains("List.concat")), "unexpected concat hint: {msgs:?}" ); }