diff --git a/CHANGELOG.md b/CHANGELOG.md index c65e43a6716..25a6a214a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ ### Compiler +- The compiler now suggest public values from imported modules when the variable + in unknown. These values are suggested based on name and arity. + + Considering this program: + ```gleam + import gleam/io + + pub fn main() -> Nil { + println("Hello, World!") + } + ``` + + The compiler will display this error message: + ```text + error: Unknown variable + ┌─ /path/to/project/src/project.gleam:4:3 + │ + 4 │ println("Hello, World!") + │ ^^^^^^^ + + The name `println` is not in scope here. + Did you mean one of these: + + - io.println + ``` + + ([raphrous](https://github.com/realraphrous)) + - The compiler now performs function inlining optimisations for a specific set of standard library functions, which can allow functions which were previously not tail-recursive on the JavaScript target to become tail-recursive. For diff --git a/compiler-core/src/error.rs b/compiler-core/src/error.rs index d2e3e7f308b..784498d794b 100644 --- a/compiler-core/src/error.rs +++ b/compiler-core/src/error.rs @@ -2402,9 +2402,9 @@ but no type in scope with that name." discarded_location, name, type_with_name_in_scope, + possible_modules, } => { let title = String::from("Unknown variable"); - if let Some(ignored_location) = discarded_location { let location = Location { label: Label { @@ -2434,17 +2434,25 @@ but no type in scope with that name." let text = if *type_with_name_in_scope { wrap_format!("`{name}` is a type, it cannot be used as a value.") } else { - let is_first_char_uppercase = - name.chars().next().is_some_and(char::is_uppercase); - - if is_first_char_uppercase { + let mut text = if name.starts_with(char::is_uppercase) { wrap_format!( - "The custom type variant constructor \ -`{name}` is not in scope here." + "The custom type variant constructor `{name}` is not in scope here." ) - } else { + } + else { wrap_format!("The name `{name}` is not in scope here.") + }; + + // If there are some suggestions about public values in imported + // modules put a "did you mean" text after the main message + if !possible_modules.is_empty() { + text.push_str("\nDid you mean one of these:\n\n"); + for module_name in possible_modules { + text.push_str(&format!(" - {module_name}.{name}\n")) + } } + + text }; Diagnostic { diff --git a/compiler-core/src/type_.rs b/compiler-core/src/type_.rs index 653fb76e94e..6e4f5370996 100644 --- a/compiler-core/src/type_.rs +++ b/compiler-core/src/type_.rs @@ -1103,6 +1103,15 @@ impl ModuleInterface { } } + pub fn get_public_function(&self, name: &str, arity: usize) -> Option<&ValueConstructor> { + self.get_public_value(name).filter(|value_constructor| { + match value_constructor.type_.fn_types() { + Some((fn_arguments_type, _)) => fn_arguments_type.len() == arity, + None => false, + } + }) + } + pub fn get_public_type(&self, name: &str) -> Option<&TypeConstructor> { let type_ = self.types.get(name)?; if type_.publicity.is_importable() { @@ -1680,6 +1689,15 @@ pub enum FieldAccessUsage { Other, } +/// This is used to know when a value is used as a call or not. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ValueUsage { + /// Used as `call(..)`, `Type(..)`, `left |> right` or `left |> right(..)` + Call { arity: usize }, + /// Used as `variable` + Other, +} + /// Verify that a value is suitable to be used as a main function. fn assert_suitable_main_function( value: &ValueConstructor, diff --git a/compiler-core/src/type_/environment.rs b/compiler-core/src/type_/environment.rs index b2e394bd3be..6b3dd194bb4 100644 --- a/compiler-core/src/type_/environment.rs +++ b/compiler-core/src/type_/environment.rs @@ -481,14 +481,22 @@ impl Environment<'_> { name: &EcoString, ) -> Result<&ValueConstructor, UnknownValueConstructorError> { match module { - None => self.scope.get(name).ok_or_else(|| { - let type_with_name_in_scope = self.module_types.keys().any(|type_| type_ == name); - UnknownValueConstructorError::Variable { + None => self + .scope + .get(name) + .ok_or_else(|| UnknownValueConstructorError::Variable { name: name.clone(), variables: self.local_value_names(), - type_with_name_in_scope, - } - }), + type_with_name_in_scope: self.module_types.keys().any(|typ| typ == name), + possible_modules: self + .imported_modules + .iter() + .filter_map(|(module_name, (_, module))| { + module.get_public_value(name).map(|_| module_name) + }) + .cloned() + .collect_vec(), + }), Some(module_name) => { let (_, module) = self.imported_modules.get(module_name).ok_or_else(|| { diff --git a/compiler-core/src/type_/error.rs b/compiler-core/src/type_/error.rs index 4e15ea45a8e..5c1f0712716 100644 --- a/compiler-core/src/type_/error.rs +++ b/compiler-core/src/type_/error.rs @@ -165,6 +165,9 @@ pub enum Error { /// this will contain its location. discarded_location: Option, type_with_name_in_scope: bool, + /// Filled with the name of imported modules when the module has public value + /// with the same name as this variable + possible_modules: Vec, }, UnknownType { @@ -1314,6 +1317,7 @@ pub enum UnknownValueConstructorError { name: EcoString, variables: Vec, type_with_name_in_scope: bool, + possible_modules: Vec, }, Module { @@ -1339,12 +1343,14 @@ pub fn convert_get_value_constructor_error( name, variables, type_with_name_in_scope, + possible_modules, } => Error::UnknownVariable { location, name, variables, discarded_location: None, type_with_name_in_scope, + possible_modules, }, UnknownValueConstructorError::Module { name, suggestions } => Error::UnknownModule { diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs index c85a1624e92..f04858b7054 100644 --- a/compiler-core/src/type_/expression.rs +++ b/compiler-core/src/type_/expression.rs @@ -432,9 +432,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { message, } => Ok(self.infer_echo(location, keyword_end, expression, message)), - UntypedExpr::Var { location, name, .. } => { - self.infer_var(name, location, ReferenceRegistration::RegisterReferences) - } + UntypedExpr::Var { location, name, .. } => self.infer_var( + name, + location, + ValueUsage::Other, + ReferenceRegistration::RegisterReferences, + ), UntypedExpr::Int { location, @@ -849,6 +852,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // We use `stacker` to prevent overflowing the stack when many `use` // expressions are chained. See https://github.com/gleam-lang/gleam/issues/4287 let infer_call = || { + // We need this in the case where `call.function` has a special call path depending + // on the type such as `UntypedExpr::Var`. In these cases, `infer_call` does not call + // `infer_or_error`. `infer_or_error` is responsible for registering warnings about + // unreachable code and thus, warnings about unreachable code are not registered. + if self.previous_panics { + self.warn_for_unreachable_code(call_location, PanicPosition::PreviousExpression); + } + self.infer_call( *call.function, call.arguments, @@ -1247,10 +1258,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> { &mut self, name: EcoString, location: SrcSpan, + var_usage: ValueUsage, register_reference: ReferenceRegistration, ) -> Result { - let constructor = - self.do_infer_value_constructor(&None, &name, &location, register_reference)?; + let constructor = self.do_infer_value_constructor( + &None, + &name, + &location, + var_usage, + register_reference, + )?; self.narrow_implementations(location, &constructor.variant)?; Ok(TypedExpr::Var { constructor, @@ -1345,6 +1362,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { UntypedExpr::Var { location, name } => self.infer_var( name, location, + ValueUsage::Other, ReferenceRegistration::DoNotRegisterReferences, ), _ => self.infer_or_error(container), @@ -2309,7 +2327,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { fn infer_clause_guard(&mut self, guard: UntypedClauseGuard) -> Result { match guard { ClauseGuard::Var { location, name, .. } => { - let constructor = self.infer_value_constructor(&None, &name, &location)?; + let constructor = + self.infer_value_constructor(&None, &name, &location, ValueUsage::Other)?; // We cannot support all values in guard expressions as the BEAM does not let definition_location = match &constructor.variant { @@ -3472,11 +3491,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> { module: &Option<(EcoString, SrcSpan)>, name: &EcoString, location: &SrcSpan, + var_usage: ValueUsage, ) -> Result { self.do_infer_value_constructor( module, name, location, + var_usage, ReferenceRegistration::RegisterReferences, ) } @@ -3486,6 +3507,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { module: &Option<(EcoString, SrcSpan)>, name: &EcoString, location: &SrcSpan, + var_usage: ValueUsage, register_reference: ReferenceRegistration, ) -> Result { let constructor = match module { @@ -3494,7 +3516,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .environment .get_variable(name) .cloned() - .ok_or_else(|| self.report_name_error(name, location))?, + .ok_or_else(|| self.report_name_error(name, location, var_usage))?, // Look in an imported module for a binding with this name Some((module_name, module_location)) => { @@ -3622,7 +3644,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } } - fn report_name_error(&mut self, name: &EcoString, location: &SrcSpan) -> Error { + fn report_name_error( + &mut self, + name: &EcoString, + location: &SrcSpan, + var_usage: ValueUsage, + ) -> Error { // First try to see if this is a module alias: // `import gleam/io` // `io.debug(io)` @@ -3633,21 +3660,49 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: *location, name: name.clone(), }, - None => Error::UnknownVariable { - location: *location, - name: name.clone(), - variables: self.environment.local_value_names(), - discarded_location: self - .environment - .discarded_names - .get(&eco_format!("_{name}")) - .cloned(), - type_with_name_in_scope: self - .environment - .module_types - .keys() - .any(|typ| typ == name), - }, + None => { + let possible_modules = match var_usage { + // This is a function call, we need to suggest a public + // value which is a function with the correct arity + ValueUsage::Call { arity } => self + .environment + .imported_modules + .iter() + .filter_map(|(module_name, (_, module))| { + module.get_public_function(name, arity).map(|_| module_name) + }) + .cloned() + .collect_vec(), + // This is a reference to a variable, we need to suggest + // a public value of any type + ValueUsage::Other => self + .environment + .imported_modules + .iter() + .filter_map(|(module_name, (_, module))| { + module.get_public_value(name).map(|_| module_name) + }) + .cloned() + .collect_vec(), + }; + + Error::UnknownVariable { + location: *location, + name: name.clone(), + variables: self.environment.local_value_names(), + discarded_location: self + .environment + .discarded_names + .get(&eco_format!("_{name}")) + .cloned(), + type_with_name_in_scope: self + .environment + .module_types + .keys() + .any(|typ| typ == name), + possible_modules, + } + } } } @@ -3705,7 +3760,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } if arguments.is_empty() => { // Type check the record constructor - let constructor = self.infer_value_constructor(&module, &name, &location)?; + let constructor = + self.infer_value_constructor(&module, &name, &location, ValueUsage::Other)?; let (tag, field_map) = match &constructor.variant { ValueConstructorVariant::Record { @@ -3744,7 +3800,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // field_map, is always None here because untyped not yet unified .. } => { - let constructor = self.infer_value_constructor(&module, &name, &location)?; + let constructor = self.infer_value_constructor( + &module, + &name, + &location, + ValueUsage::Call { + arity: arguments.len(), + }, + )?; let (tag, field_map, variant_index) = match &constructor.variant { ValueConstructorVariant::Record { @@ -3886,7 +3949,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => { // Infer the type of this constant - let constructor = self.infer_value_constructor(&module, &name, &location)?; + let constructor = + self.infer_value_constructor(&module, &name, &location, ValueUsage::Other)?; match constructor.variant { ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::LocalConstant { .. } @@ -4063,6 +4127,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { kind: CallKind, ) -> (TypedExpr, Vec, Arc) { let fun = match fun { + UntypedExpr::Var { location, name } => { + self.infer_called_var(name, location, arguments.len()) + } + UntypedExpr::FieldAccess { label, container, @@ -4100,6 +4168,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> { (fun, arguments, type_) } + pub fn infer_called_var( + &mut self, + name: EcoString, + location: SrcSpan, + arity: usize, + ) -> TypedExpr { + match self.infer_var( + name, + location, + ValueUsage::Call { arity }, + ReferenceRegistration::RegisterReferences, + ) { + Ok(typed_expr) => typed_expr, + Err(error) => { + self.problems.error(error); + self.error_expr(location) + } + } + } + fn infer_fn_with_call_context( &mut self, arguments: Vec, diff --git a/compiler-core/src/type_/pattern.rs b/compiler-core/src/type_/pattern.rs index 915296ed804..49dbebdf964 100644 --- a/compiler-core/src/type_/pattern.rs +++ b/compiler-core/src/type_/pattern.rs @@ -1259,6 +1259,15 @@ impl<'a, 'b> PatternTyper<'a, 'b> { .module_types .keys() .any(|type_| type_ == &name), + possible_modules: self + .environment + .imported_modules + .iter() + .filter_map(|(module_name, (_, module))| { + module.get_public_value(&name).map(|_| module_name) + }) + .cloned() + .collect_vec(), }); } }, diff --git a/compiler-core/src/type_/pipe.rs b/compiler-core/src/type_/pipe.rs index a36a8bb976c..244665c5ef4 100644 --- a/compiler-core/src/type_/pipe.rs +++ b/compiler-core/src/type_/pipe.rs @@ -139,18 +139,24 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { location, .. } => { - let fun = match self.expr_typer.infer_or_error(*fun) { - Ok(fun) => fun, - Err(e) => { - // In case we cannot infer the function we'll - // replace it with an invalid expression with an - // unbound type to keep going! - self.expr_typer.problems.error(e); - TypedExpr::Invalid { - location, - type_: self.expr_typer.new_unbound_var(), - } + let fun = match *fun { + UntypedExpr::Var { location, name } => { + self.expr_typer + .infer_called_var(name, location, arguments.len() + 1) } + untyped_fun => match self.expr_typer.infer_or_error(untyped_fun) { + Ok(typed_fun) => typed_fun, + Err(e) => { + // In case we cannot infer the function we'll + // replace it with an invalid expression with an + // unbound type to keep going! + self.expr_typer.problems.error(e); + TypedExpr::Invalid { + location, + type_: self.expr_typer.new_unbound_var(), + } + } + }, }; match fun.type_().fn_types() { @@ -350,17 +356,13 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { /// b is the `function` argument. fn infer_apply_pipe(&mut self, function: UntypedExpr) -> TypedExpr { let function_location = function.location(); - let function = Box::new(match self.expr_typer.infer_or_error(function) { - Ok(function) => function, - Err(error) => { - // If we cannot infer the function we put an invalid expression - // in its place so we can still keep going with the other steps. - self.expr_typer.problems.error(error); - TypedExpr::Invalid { - location: function_location, - type_: self.expr_typer.new_unbound_var(), - } + // If the function is used as a variable, then we can suggest values + // from imported modules with the same name and same arity. + let function = Box::new(match function { + UntypedExpr::Var { location, name } => { + self.expr_typer.infer_called_var(name, location, 1) } + _ => self.expr_typer.infer(function), }); self.expr_typer.purity = self diff --git a/compiler-core/src/type_/tests/errors.rs b/compiler-core/src/type_/tests/errors.rs index 2ba6d7a5534..3a5bc619a64 100644 --- a/compiler-core/src/type_/tests/errors.rs +++ b/compiler-core/src/type_/tests/errors.rs @@ -3308,3 +3308,249 @@ pub fn main() { " ); } + +#[test] +fn unknown_variable_possible_modules_1() { + assert_module_error!( + ("module", "pub const one = 1"), + " +import module +pub fn main() { + one +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_2() { + assert_module_error!( + ("module", "pub fn add(x: Int, y: Int) { x + y }"), + " +import module +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_3() { + assert_module_error!( + ("module", "pub fn add(x: Int) { x + 1 }"), + " +import module +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_4() { + assert_module_error!( + ("module", "pub fn add(x: Float, y: Float) { x +. y }"), + " +import module +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_5() { + assert_module_error!( + ( + "module", + " +fn add(x: Int, y: Int) { x + y } +" + ), + " +import module +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_6() { + assert_module_error!( + ( + "module", + " +pub const add = internal_add +fn internal_add(x: Int, y: Int) { x + y } +" + ), + " +import module +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_7() { + assert_module_error!( + ( + "module", + " +pub type OneOrTwo { + One + Two +} +" + ), + " +import module +pub fn main() { + One +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_8() { + assert_module_error!( + ( + "moduleone", + " +pub type MyType { + MyRecord(x: Int, y: Int) +} +" + ), + ( + "moduletwo", + " +pub type AnotherType { + MyRecord(x: Int) +} +" + ), + " +import moduleone +import moduletwo +pub fn main() { + MyRecord(1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_9() { + assert_module_error!( + ( + "module", + " +pub fn add(x: Int) { + x + 1 +} +" + ), + " +import module +pub fn main() { + 1 |> add +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_10() { + assert_module_error!( + ( + "module", + " +pub fn add(x: Int, y: Int) { + x + y +} +" + ), + " +import module +pub fn main() { + 1 |> add(2) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_11() { + assert_module_error!( + ( + "module", + " +pub fn add(x: Int) { + fn(y: Int) { x + y } +} +" + ), + " +import module +pub fn main() { + 1 |> add(2) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_12() { + assert_module_error!( + ( + "module", + " +pub fn wibble(_) { + 1 +} +" + ), + " +import module +pub fn main() { + use <- wibble + 1 +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_13() { + assert_module_error!( + ("module", "pub fn add(x: Int, y: Int) { x + y }"), + " +import module as wibble +pub fn main() { + add(1, 1) +} +" + ); +} + +#[test] +fn unknown_variable_possible_modules_14() { + assert_module_error!( + ("gleam/module", "pub fn add(x: Int, y: Int) { x + y }"), + " +import gleam/module +pub fn main() { + add(1, 1) +} +" + ); +} diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times.snap index 283a8505862..13472063940 100644 --- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times.snap +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times.snap @@ -35,3 +35,6 @@ error: Unknown variable │ ^^^ The name `zoo` is not in scope here. +Did you mean one of these: + + - wibble.zoo diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_1.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_1.snap new file mode 100644 index 00000000000..96c9f15f014 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_1.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n one\n}\n" +--- +----- SOURCE CODE +-- module.gleam +pub const one = 1 + +-- main.gleam + +import module +pub fn main() { + one +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ one + │ ^^^ + +The name `one` is not in scope here. +Did you mean one of these: + + - module.one diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_10.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_10.snap new file mode 100644 index 00000000000..37c98f3137d --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_10.snap @@ -0,0 +1,31 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n 1 |> add(2)\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub fn add(x: Int, y: Int) { + x + y +} + + +-- main.gleam + +import module +pub fn main() { + 1 |> add(2) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:10 + │ +4 │ 1 |> add(2) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_11.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_11.snap new file mode 100644 index 00000000000..d1dad471a7f --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_11.snap @@ -0,0 +1,28 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n 1 |> add(2)\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub fn add(x: Int) { + fn(y: Int) { x + y } +} + + +-- main.gleam + +import module +pub fn main() { + 1 |> add(2) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:10 + │ +4 │ 1 |> add(2) + │ ^^^ + +The name `add` is not in scope here. diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_12.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_12.snap new file mode 100644 index 00000000000..ec21e5b6e99 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_12.snap @@ -0,0 +1,32 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n use <- wibble\n 1\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub fn wibble(_) { + 1 +} + + +-- main.gleam + +import module +pub fn main() { + use <- wibble + 1 +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:12 + │ +4 │ use <- wibble + │ ^^^^^^ + +The name `wibble` is not in scope here. +Did you mean one of these: + + - module.wibble diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_13.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_13.snap new file mode 100644 index 00000000000..f85f37d7f9a --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_13.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module as wibble\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam +pub fn add(x: Int, y: Int) { x + y } + +-- main.gleam + +import module as wibble +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - wibble.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_14.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_14.snap new file mode 100644 index 00000000000..7b94d72e39b --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_14.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport gleam/module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- gleam/module.gleam +pub fn add(x: Int, y: Int) { x + y } + +-- main.gleam + +import gleam/module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_2.snap new file mode 100644 index 00000000000..b7cf032e862 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_2.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam +pub fn add(x: Int, y: Int) { x + y } + +-- main.gleam + +import module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_3.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_3.snap new file mode 100644 index 00000000000..0273a066daa --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_3.snap @@ -0,0 +1,24 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam +pub fn add(x: Int) { x + 1 } + +-- main.gleam + +import module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_4.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_4.snap new file mode 100644 index 00000000000..be57a6be9b4 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_4.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam +pub fn add(x: Float, y: Float) { x +. y } + +-- main.gleam + +import module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_5.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_5.snap new file mode 100644 index 00000000000..49d3b6121e3 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_5.snap @@ -0,0 +1,26 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +fn add(x: Int, y: Int) { x + y } + + +-- main.gleam + +import module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_6.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_6.snap new file mode 100644 index 00000000000..8ebb2ffae30 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_6.snap @@ -0,0 +1,30 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n add(1, 1)\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub const add = internal_add +fn internal_add(x: Int, y: Int) { x + y } + + +-- main.gleam + +import module +pub fn main() { + add(1, 1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ add(1, 1) + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_7.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_7.snap new file mode 100644 index 00000000000..e33cde5b595 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_7.snap @@ -0,0 +1,32 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n One\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub type OneOrTwo { + One + Two +} + + +-- main.gleam + +import module +pub fn main() { + One +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:5 + │ +4 │ One + │ ^^^ + +The custom type variant constructor `One` is not in scope here. +Did you mean one of these: + + - module.One diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_8.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_8.snap new file mode 100644 index 00000000000..393acd338c7 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_8.snap @@ -0,0 +1,39 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport moduleone\nimport moduletwo\npub fn main() {\n MyRecord(1)\n}\n" +--- +----- SOURCE CODE +-- moduleone.gleam + +pub type MyType { + MyRecord(x: Int, y: Int) +} + + +-- moduletwo.gleam + +pub type AnotherType { + MyRecord(x: Int) +} + + +-- main.gleam + +import moduleone +import moduletwo +pub fn main() { + MyRecord(1) +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:5:5 + │ +5 │ MyRecord(1) + │ ^^^^^^^^ + +The custom type variant constructor `MyRecord` is not in scope here. +Did you mean one of these: + + - moduletwo.MyRecord diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_9.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_9.snap new file mode 100644 index 00000000000..68cf7ea57b7 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_possible_modules_9.snap @@ -0,0 +1,31 @@ +--- +source: compiler-core/src/type_/tests/errors.rs +expression: "\nimport module\npub fn main() {\n 1 |> add\n}\n" +--- +----- SOURCE CODE +-- module.gleam + +pub fn add(x: Int) { + x + 1 +} + + +-- main.gleam + +import module +pub fn main() { + 1 |> add +} + + +----- ERROR +error: Unknown variable + ┌─ /src/one/two.gleam:4:10 + │ +4 │ 1 |> add + │ ^^^ + +The name `add` is not in scope here. +Did you mean one of these: + + - module.add diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_1.snap similarity index 74% rename from compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic.snap rename to compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_1.snap index 5d01624f481..d5402292651 100644 --- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic.snap +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_1.snap @@ -14,9 +14,10 @@ expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n ----- WARNING warning: Unreachable code - ┌─ /src/warning/wrn.gleam:5:20 - │ -5 │ use <- wibble - │ ^^^^^^ + ┌─ /src/warning/wrn.gleam:5:13 + │ +5 │ ╭ use <- wibble +6 │ │ 1 + │ ╰─────────────^ This code is unreachable because it comes after a `panic`. diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_2.snap new file mode 100644 index 00000000000..fc4487191a8 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic_2.snap @@ -0,0 +1,27 @@ +--- +source: compiler-core/src/type_/tests/warnings.rs +expression: "\n import module\n pub fn a() {\n panic\n use <- module.wibble\n 1\n }\n " +--- +----- SOURCE CODE +-- module.gleam +pub fn wibble(_) { 1 } + +-- main.gleam + + import module + pub fn a() { + panic + use <- module.wibble + 1 + } + + +----- WARNING +warning: Unreachable code + ┌─ /src/warning/wrn.gleam:5:13 + │ +5 │ ╭ use <- module.wibble +6 │ │ 1 + │ ╰─────────────^ + +This code is unreachable because it comes after a `panic`. diff --git a/compiler-core/src/type_/tests/warnings.rs b/compiler-core/src/type_/tests/warnings.rs index 455d616e6c2..f346f01803e 100644 --- a/compiler-core/src/type_/tests/warnings.rs +++ b/compiler-core/src/type_/tests/warnings.rs @@ -1921,7 +1921,7 @@ fn doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_2() { } #[test] -fn unreachable_use_after_panic() { +fn unreachable_use_after_panic_1() { assert_warning!( r#" pub fn wibble(_) { 1 } @@ -1934,6 +1934,21 @@ fn unreachable_use_after_panic() { ); } +#[test] +fn unreachable_use_after_panic_2() { + assert_warning!( + ("package", "module", r#"pub fn wibble(_) { 1 }"#), + r#" + import module + pub fn a() { + panic + use <- module.wibble + 1 + } + "# + ); +} + #[test] fn unreachable_code_after_case_subject_panics_1() { assert_warning!(