Skip to content
Draft
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 16 additions & 8 deletions compiler-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions compiler-core/src/type_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,15 @@ impl ModuleInterface {
}
}

pub fn get_public_function(&self, name: &str, arity: usize) -> Option<&ValueConstructor> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document this function please 🙏

Does this return internal values too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this function also return internal values. This is based on the function get_public_value that check for importable values, and importable values can be public and internal. I named this function like that to be consistent with other function of the same kind.

Also gonna document this function but get_public_value, on which this function is based, is not documented ...

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() {
Expand Down Expand Up @@ -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(..)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Used as `call(..)`, `Type(..)`, `left |> right` or `left |> right(..)`
/// Used as `function(..)`, `Record(..)`, `left |> right` or `left |> right(..)`

It's not a type, it's value, specifically a record.

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,
Expand Down
20 changes: 14 additions & 6 deletions compiler-core/src/type_/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(|| {
Expand Down
6 changes: 6 additions & 0 deletions compiler-core/src/type_/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ pub enum Error {
/// this will contain its location.
discarded_location: Option<SrcSpan>,
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<EcoString>,
},

UnknownType {
Expand Down Expand Up @@ -1314,6 +1317,7 @@ pub enum UnknownValueConstructorError {
name: EcoString,
variables: Vec<EcoString>,
type_with_name_in_scope: bool,
possible_modules: Vec<EcoString>,
},

Module {
Expand All @@ -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 {
Expand Down
Loading
Loading