Skip to content

Add suggestions for variables with the same name in imported modules when unknown variable #4810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

realraphrous
Copy link

@realraphrous realraphrous commented Jul 28, 2025

closes #2697

As stated on this Discord message, this is my first contributions to OSS and this project. I would really appreciate any feedback and improvement on my code, this pull request or commits that I made !

Summary

This PR add more suggestions when an unknown variable is encounter by listing all variables / identifier in imported modules that match the same name. Also, when the variable / identifier is called such as functions, pipeline steps or type constructors, the suggestions are based on the name and number of arguments provided to the call. (ref: #2697 (comment))

Examples

For example, take this invalid program:

import gleam/io

pub fn main() -> Nil {
  println("Hello, World!)
}

The error message reported by the compiler:

error: Unknown variable
  ┌─ /home/raphrous/code/gleam/module_suggestion/src/mylib/mymodule.gleam:4:3
  │
4 │   println("Hello, World!")
  │   ^^^^^^^

The name `println` is not in scope here.
Consider using one of these variables:

    io.println

Also, it works with variables, type constructor, constant etc. Consider this custom module and this program:

// mylib/mymodule
pub const my_const = 8

pub type MyType {
  Some(x: Int, y: Int)
}

// main file
import gleam/option
import mylib/mymodule

pub fn main() -> Nil {
  // option.Some need to be suggested but 
  // not mymodule.Some (cause of arity)
  Some(my_const)
}

With these changes we have new suggestions about variables / identifier in imported modules:

error: Unknown variable
  ┌─ /path/to/project/src/project.gleam:7:3
  │
7 │   Some(my_const)
  │   ^^^^

The custom type variant constructor `Some` is not in scope here.
Consider using one of these variables:

    option.Some


error: Unknown variable
  ┌─ /path/to/project/src/project.gleam:7:8
  │
7 │   Some(my_const)
  │        ^^^^^^^^

The name `my_const` is not in scope here.
Consider using one of these variables:

    mymodule.my_const

Implementations

The current implementation uses a new enumeration VarUsage. This is similar to the enum FieldAccessUsage and it helps the function report_name_error by giving a little more context about the usage of this variable: a function call or something else. This function can now report about imported modules containing a variable with the same name as the unknown variable and, in case of function call, the same arity.

I tried to experiment another implementation where the function report_name_error was not provided with more context. The function was keeped as simple as possible by reporting imported modules containing a variable with the same name, without checking if it was a call or something else. Instead, we were filtering at the call side, inside infer_call function for example, if the variable from the imported module was of type "function-like".
Honestly it does not feel right. We needed to check if an UnknownVariable error was emitted, apply a filtering on imported modules that were reported and re-emit the same error with the filter applied. Also, we had to perform this manipulation at every location where a function call was possible, which likely implies code duplication, errors and omissions.

Shortcomings

Wording

First thing first, the wording. I am not sure about the error message wording:

  • Consider using one of these variables

For many developers, io.println is a function and uri.empty is a constant, not really variables. I wanted to make the error message like:

  • Consider using on of these identifiers

But the term identifiers is not used in the compiler, in error messages that I found or the language tour so it does not feel right. As I can see in the compiler, Var refer to what I call identifier so I thought it would be best to write variables.

No type checking on arguments

Because we only check name and arity in the case of calls, the suggestions may be wrong because the type of the function parameters does not match the type of arguments provided with the call. If we want that, we need to give more context, such as arguments types, to the report_name_error function in order to find the right functions to suggest.

Suggestions for pipeline calls like 1 |> add(2)

Also, I had a problem with function call on pipeline. To better explain the situation, take this program:

pub fn main() -> Nil {
  1 |> add(2)
}

It can be desugared in two ways:

pub fn main() -> Nil {
  // First possibility
  add(1, 2)
  // Second possibility
  add(2)(1)
}

I didn't know how to implement this case. For sure, the compiler can report function with arity of 2 and a function with arity 1 that has a return type that is also a function with arity 1. I just didn't know what was the right choice ...

Tests

To conclude, theses commits does not add tests for now, it might be cool to have some !

Ending

Thank you in advance for reading and helping me on my journey into OSS. Please feel free to give me feedback on all this work! 💛

@realraphrous realraphrous changed the title Better suggestions imported modules Add suggestions for variables with the same name in imported modules when unknown variable Jul 28, 2025
When a variable is not in the current scope, an `Error::UnknownVariable`
is triggered. However the only suggestions were for variables in the
scope with a similar name computed by the "did you mean" algorithm. With
this commit, we also suggest public variables / identifiers (such as
constants, functions or type variant constructor) in imported modules
with the same name.

For example with the following code:
```gleam
import gleam/io

pub fn main() -> Nil {
  println("Hello, World!")
}
```

The suggestions are:
```
  ┌─ /path/to/project/src/project.gleam:4:3
  │
4 │   println("Hello, World!")
  │   ^^^^^^^

The name `println` is not in scope here.
Consider using one of these variables:

        io.println
```

However because we are only checking the name of the variable, we could
have wrong suggestions, as in this case:
```gleam
import gleam/float
import gleam/int

pub fn main() -> Nil {
  to_string(3)
}
```

Here, it is clear that we want suggestions on a function named
`to_string` and accepting one parameter of type `Int`, but this is not
the case:
```
  ┌─ /path/to/project/src/project.gleam:5:3
  │
5 │   to_string(3)
  │   ^^^^^^^^^

The name `to_string` is not in scope here.
Consider using one of these implementations:

        float.to_string
        int.to_string
```
@realraphrous realraphrous force-pushed the better-suggestions-imported-modules branch 2 times, most recently from 801601c to e292510 Compare July 28, 2025 15:06
Before this commit, all functions / constructors were suggested even
if they do not have the correct arity. With this change, only functions
/ constructors with the correct arity are suggested. However, even if
the arity is correct the type of arguments can mismatch, resulting in
incorrect suggestions.

```gleam
// fn to_string(Int) -> String
import gleam/int
// fn to_string(Float) -> String
import gleam/float

pub fn main() -> Nil {
  to_string(1)
}



error: Unknown variable
  ┌─ /path/to/project/src/project.gleam:8:3
  │
8 │   to_string(1)
  │   ^^^^^^^^^

The name `to_string` is not in scope here.
Consider using one of these variables:

    int.to_string
    float.to_string
```
At this stage, pipeline are not considered as `Call` so previous work
on `UntypedExpr::Call` does not apply directly to pipeline. With this
change, we handle the pipeline case when the function / constructor is
not a `Call`.

```gleam
1 |> to_string
```

When there is a call in the pipeline, it is more delicate to address
because something like that:
```gleam
1 |> add(2)
```
Can be desugared into two possibilities and so suggestions are not the
same. The function `report_name_error` does not have enough context to
make good suggestions:
```gleam
add(1, 2)
add(2)(1)
```

In this case, we just suggest every functions.
In this example, the name `zoo` exist in the module `wibble` which is
imported. The compiler correctly suggest to use `wibble.zoo`.
@realraphrous realraphrous force-pushed the better-suggestions-imported-modules branch from e292510 to 53c6c08 Compare July 29, 2025 11:45
@realraphrous realraphrous marked this pull request as draft July 29, 2025 13:03
@realraphrous realraphrous marked this pull request as ready for review July 29, 2025 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Suggest functions in imported modules with the same name as an unknown variable
1 participant