⭐️ A friendly language for building type-safe, scalable systems!

improve error message for variable not in scope when there's discarded variables

authored by giacomocavalieri.me and committed by Louis Pilfold 1b06e2f9 c73ae495

+17 -5
compiler-core/src/analyse.rs
··· 550 550 has_javascript_external: external_javascript.is_some(), 551 551 }; 552 552 553 - let typed_args = arguments 554 - .into_iter() 555 - .zip(&prereg_args_types) 556 - .map(|(a, t)| a.set_type(t.clone())) 557 - .collect_vec(); 553 + let mut typed_args = Vec::with_capacity(arguments.len()); 554 + for (argument, type_) in arguments.into_iter().zip(&prereg_args_types) { 555 + let argument = argument.set_type(type_.clone()); 556 + match &argument.names { 557 + ast::ArgNames::Named { .. } | ast::ArgNames::NamedLabelled { .. } => (), 558 + ast::ArgNames::Discard { name, location } 559 + | ast::ArgNames::LabelledDiscard { 560 + name, 561 + name_location: location, 562 + .. 563 + } => { 564 + let _ = environment.ignored_names.insert(name.clone(), *location); 565 + } 566 + } 567 + 568 + typed_args.push(argument); 569 + } 558 570 559 571 // We have already registered the function in the `register_value_from_function` 560 572 // method, but here we must set this as the current function again, so that anything
+33 -8
compiler-core/src/error.rs
··· 12 12 use crate::type_::{FieldAccessUsage, error::PatternMatchKind}; 13 13 use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type}; 14 14 use crate::{bit_array, diagnostic::Level, javascript, type_::UnifyErrorSituation}; 15 - use ecow::EcoString; 15 + use ecow::{EcoString, eco_format}; 16 16 use itertools::Itertools; 17 17 use pubgrub::Package; 18 18 use pubgrub::{DerivationTree, VersionSet}; ··· 2388 2388 TypeError::UnknownVariable { 2389 2389 location, 2390 2390 variables, 2391 + ignored_variables, 2391 2392 name, 2392 2393 type_with_name_in_scope, 2393 2394 } => { ··· 2402 2403 wrap_format!("The name `{name}` is not in scope here.") 2403 2404 } 2404 2405 }; 2405 - Diagnostic { 2406 - title: "Unknown variable".into(), 2407 - text, 2408 - hint: None, 2409 - level: Level::Error, 2410 - location: Some(Location { 2406 + 2407 + let ignored_variable = ignored_variables.get(&eco_format!("_{name}")); 2408 + let location = if let Some(ignored_location) = ignored_variable { 2409 + Location { 2410 + label: Label { 2411 + text: None, 2412 + span: *location, 2413 + }, 2414 + path: path.clone(), 2415 + src: src.clone(), 2416 + extra_labels: vec![ 2417 + ExtraLabel { 2418 + src_info: None, 2419 + label: Label { 2420 + text: Some("Did you mean to use this ignored variable?".into()), 2421 + span: *ignored_location, 2422 + } 2423 + } 2424 + ], 2425 + } 2426 + } else { 2427 + Location { 2411 2428 label: Label { 2412 2429 text: did_you_mean(name, variables), 2413 2430 span: *location, ··· 2415 2432 path: path.clone(), 2416 2433 src: src.clone(), 2417 2434 extra_labels: vec![], 2418 - }), 2435 + } 2436 + }; 2437 + 2438 + Diagnostic { 2439 + title: "Unknown variable".into(), 2440 + text, 2441 + hint: None, 2442 + level: Level::Error, 2443 + location: Some(location), 2419 2444 } 2420 2445 } 2421 2446
+18 -2
compiler-core/src/type_/environment.rs
··· 56 56 /// Values defined in the current function (or the prelude) 57 57 pub scope: im::HashMap<EcoString, ValueConstructor>, 58 58 59 + // The names of all the ignored variables and arguments in scope: 60 + // `let _var = 10` `pub fn main(_var) { todo }`. 61 + pub ignored_names: HashMap<EcoString, SrcSpan>, 62 + 59 63 /// Types defined in the current module (or the prelude) 60 64 pub module_types: HashMap<EcoString, TypeConstructor>, 61 65 ··· 133 137 unqualified_imported_types: HashMap::new(), 134 138 accessors: prelude.accessors.clone(), 135 139 scope: prelude.values.clone().into(), 140 + ignored_names: HashMap::new(), 136 141 importable_modules, 137 142 current_module, 138 143 local_variable_usages: vec![HashMap::new()], ··· 148 153 #[derive(Debug)] 149 154 pub struct ScopeResetData { 150 155 local_values: im::HashMap<EcoString, ValueConstructor>, 156 + ignored_names: HashMap<EcoString, SrcSpan>, 151 157 } 152 158 153 159 impl Environment<'_> { ··· 170 176 171 177 pub fn open_new_scope(&mut self) -> ScopeResetData { 172 178 let local_values = self.scope.clone(); 179 + let ignored_names = self.ignored_names.clone(); 173 180 self.local_variable_usages.push(HashMap::new()); 174 - ScopeResetData { local_values } 181 + ScopeResetData { 182 + local_values, 183 + ignored_names, 184 + } 175 185 } 176 186 177 187 pub fn close_scope( ··· 180 190 was_successful: bool, 181 191 problems: &mut Problems, 182 192 ) { 193 + let ScopeResetData { 194 + local_values, 195 + ignored_names, 196 + } = data; 197 + 183 198 let unused = self 184 199 .local_variable_usages 185 200 .pop() ··· 192 207 if was_successful { 193 208 self.handle_unused_variables(unused, problems); 194 209 } 195 - self.scope = data.local_values; 210 + self.scope = local_values; 211 + self.ignored_names = ignored_names; 196 212 } 197 213 198 214 pub fn next_uid(&mut self) -> u64 {
+3 -1
compiler-core/src/type_/error.rs
··· 14 14 use num_bigint::BigInt; 15 15 #[cfg(test)] 16 16 use pretty_assertions::assert_eq; 17 - use std::sync::Arc; 17 + use std::{collections::HashMap, sync::Arc}; 18 18 19 19 /// Errors and warnings discovered when compiling a module. 20 20 /// ··· 167 167 location: SrcSpan, 168 168 name: EcoString, 169 169 variables: Vec<EcoString>, 170 + ignored_variables: HashMap<EcoString, SrcSpan>, 170 171 type_with_name_in_scope: bool, 171 172 }, 172 173 ··· 1297 1298 location, 1298 1299 name, 1299 1300 variables, 1301 + ignored_variables: HashMap::new(), 1300 1302 type_with_name_in_scope, 1301 1303 }, 1302 1304
+11
compiler-core/src/type_/expression.rs
··· 1008 1008 .map(|type_| self.type_from_ast(&type_)) 1009 1009 .unwrap_or_else(|| Ok(self.new_unbound_var()))?; 1010 1010 1011 + match &names { 1012 + ArgNames::Named { .. } | ArgNames::NamedLabelled { .. } => (), 1013 + ArgNames::Discard { name, .. } | ArgNames::LabelledDiscard { name, .. } => { 1014 + let _ = self 1015 + .environment 1016 + .ignored_names 1017 + .insert(name.clone(), location); 1018 + } 1019 + } 1020 + 1011 1021 // If we know the expected type of the argument from its contextual 1012 1022 // usage then unify the newly constructed type with the expected type. 1013 1023 // We do this here because then there is more type information for the ··· 3469 3479 location: *location, 3470 3480 name: name.clone(), 3471 3481 variables: self.environment.local_value_names(), 3482 + ignored_variables: self.environment.ignored_names.clone(), 3472 3483 type_with_name_in_scope: self 3473 3484 .environment 3474 3485 .module_types
+13 -6
compiler-core/src/type_/pattern.rs
··· 586 586 match pattern { 587 587 Pattern::Discard { name, location, .. } => { 588 588 self.check_name_case(location, &name, Named::Discard); 589 + let _ = self 590 + .environment 591 + .ignored_names 592 + .insert(name.clone(), location); 589 593 Pattern::Discard { 590 594 type_, 591 595 name, ··· 613 617 614 618 Pattern::VarUsage { name, location, .. } => { 615 619 let constructor = match self.variables.get_mut(&name) { 616 - // If we've bound a variable in the current bit array pattern, we 617 - // want to use that. 620 + // If we've bound a variable in the current bit array pattern, 621 + // we want to use that. 618 622 Some(variable) if variable.in_scope() => { 619 623 variable.usage = Usage::UsedInPattern; 620 624 ValueConstructor::local_variable( ··· 631 635 location, 632 636 name: name.clone(), 633 637 variables: self.environment.local_value_names(), 638 + ignored_variables: self.environment.ignored_names.clone(), 634 639 type_with_name_in_scope: self 635 640 .environment 636 641 .module_types ··· 695 700 }, 696 701 ); 697 702 } 698 - AssignName::Discard(_) => { 699 - if let AssignName::Discard(right) = &right_side_assignment { 700 - self.check_name_case(right_location, right, Named::Discard); 701 - } 703 + AssignName::Discard(right) => { 704 + let _ = self 705 + .environment 706 + .ignored_names 707 + .insert(right.clone(), right_location); 708 + self.check_name_case(right_location, right, Named::Discard); 702 709 } 703 710 }; 704 711
+67
compiler-core/src/type_/tests/errors.rs
··· 3125 3125 fn non_utf8_string_assignment() { 3126 3126 assert_error!(r#"let assert <<"Hello" as message:utf16>> = <<>>"#); 3127 3127 } 3128 + 3129 + #[test] 3130 + fn shadowed_function_argument() { 3131 + assert_module_error!( 3132 + " 3133 + pub fn go(_x) { 3134 + x + 1 3135 + } 3136 + " 3137 + ); 3138 + } 3139 + 3140 + #[test] 3141 + fn shadowed_fn_argument() { 3142 + assert_module_error!( 3143 + " 3144 + pub fn go(x) { 3145 + fn(_y) { 3146 + y + x 3147 + } 3148 + } 3149 + " 3150 + ); 3151 + } 3152 + 3153 + #[test] 3154 + fn shadowed_let_variable() { 3155 + assert_module_error!( 3156 + " 3157 + pub fn go() { 3158 + let _x = 1 3159 + x + 1 3160 + } 3161 + " 3162 + ); 3163 + } 3164 + 3165 + #[test] 3166 + fn shadowed_pattern_variable() { 3167 + assert_module_error!( 3168 + " 3169 + pub type Wibble { 3170 + Wibble(Int) 3171 + } 3172 + 3173 + pub fn go(x) { 3174 + case x { 3175 + Wibble(_y) -> y + 1 3176 + } 3177 + } 3178 + " 3179 + ); 3180 + } 3181 + 3182 + #[test] 3183 + fn do_not_suggest_ignored_variable_outside_of_current_scope() { 3184 + assert_module_error!( 3185 + " 3186 + pub fn go() { 3187 + let _ = { 3188 + let _y = 1 // <- this shouldn't be highlighted! 3189 + } 3190 + y 3191 + } 3192 + " 3193 + ); 3194 + }
+22
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__do_not_suggest_ignored_variable_outside_of_current_scope.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub fn go() {\n let _ = {\n let _y = 1 // <- this shouldn't be highlighted!\n }\n y\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go() { 8 + let _ = { 9 + let _y = 1 // <- this shouldn't be highlighted! 10 + } 11 + y 12 + } 13 + 14 + 15 + ----- ERROR 16 + error: Unknown variable 17 + ┌─ /src/one/two.gleam:6:3 18 + 19 + 6 │ y 20 + │ ^ 21 + 22 + The name `y` is not in scope here.
+23
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_fn_argument.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub fn go(x) {\n fn(_y) {\n y + x\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go(x) { 8 + fn(_y) { 9 + y + x 10 + } 11 + } 12 + 13 + 14 + ----- ERROR 15 + error: Unknown variable 16 + ┌─ /src/one/two.gleam:4:5 17 + 18 + 3 │ fn(_y) { 19 + │ -- Did you mean to use this ignored variable? 20 + 4 │ y + x 21 + │ ^ 22 + 23 + The name `y` is not in scope here.
+21
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_function_argument.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub fn go(_x) {\n x + 1\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go(_x) { 8 + x + 1 9 + } 10 + 11 + 12 + ----- ERROR 13 + error: Unknown variable 14 + ┌─ /src/one/two.gleam:3:3 15 + 16 + 2 │ pub fn go(_x) { 17 + │ -- Did you mean to use this ignored variable? 18 + 3 │ x + 1 19 + │ ^ 20 + 21 + The name `x` is not in scope here.
+22
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_let_variable.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub fn go() {\n let _x = 1\n x + 1\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn go() { 8 + let _x = 1 9 + x + 1 10 + } 11 + 12 + 13 + ----- ERROR 14 + error: Unknown variable 15 + ┌─ /src/one/two.gleam:4:3 16 + 17 + 3 │ let _x = 1 18 + │ -- Did you mean to use this ignored variable? 19 + 4 │ x + 1 20 + │ ^ 21 + 22 + The name `x` is not in scope here.
+27
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_pattern_variable.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/errors.rs 3 + expression: "\npub type Wibble {\n Wibble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(_y) -> y + 1\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub type Wibble { 8 + Wibble(Int) 9 + } 10 + 11 + pub fn go(x) { 12 + case x { 13 + Wibble(_y) -> y + 1 14 + } 15 + } 16 + 17 + 18 + ----- ERROR 19 + error: Unknown variable 20 + ┌─ /src/one/two.gleam:8:19 21 + 22 + 8 │ Wibble(_y) -> y + 1 23 + │ -- ^ 24 + │ │ 25 + │ Did you mean to use this ignored variable? 26 + 27 + The name `y` is not in scope here.