Skip to content

Display original function definition generic names on function signature helper when arguments are unbound #4574

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

Closed
wants to merge 1 commit into from

Conversation

scristobal
Copy link
Contributor

@scristobal scristobal commented May 9, 2025

While working on #4570, I noticed that generics were not displayed with the original names, so I also changed that. Hope the feature is interesting.

  • The function signature helper now displays original function definition
    generic names when arguments are unbound. For example, in this code:

    pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
    
    pub fn main() {
        wibble( )
            // ↑ triggering signature help here, will produce:
            //      wibble(something, fn() -> something, anything)
            // instead of
            //      wibble(a, fn() -> a, b) -> Nil
    }

link to snapshot

It also works in scenarios where not all parameters are unbounded.

pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }

pub fn main() {
    wibble(1, )
          // ↑ triggering signature help here, will produce:
          //     wibble(Int, fn() -> Int, anything) -> Nil
          // instead of
          //     wibble(Int, fn() -> Int, a) -> Nil
}

link to snapshot

@scristobal scristobal force-pushed the issue-4570 branch 4 times, most recently from 0a9b3af to 59efbf3 Compare May 11, 2025 21:05
@scristobal scristobal changed the title use the new Printer in signature help Display function definition generic names on function signature helper when arguments are unbound May 12, 2025
@scristobal scristobal changed the title Display function definition generic names on function signature helper when arguments are unbound Display original function definition generic names on function signature helper when arguments are unbound May 12, 2025
@scristobal scristobal force-pushed the issue-4570 branch 2 times, most recently from a61a098 to e88c128 Compare May 12, 2025 04:45
@scristobal scristobal marked this pull request as ready for review May 12, 2025 04:48
@scristobal scristobal requested a review from GearsDatapacks May 12, 2025 05:13
Copy link
Member

@GearsDatapacks GearsDatapacks left a comment

Choose a reason for hiding this comment

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

Thank you! Looks good! I've left a comment about the implementation of this inline.

pub fn main() {
wibble( )
// ↑ triggering signature help here, will produce:
// wibble(something, fn() -> something, anything)
Copy link
Member

Choose a reason for hiding this comment

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

I find it easier to read when the displayed code is in a separate codeblock, rather than a comment.

// Find the arguments of the function definition, this
// is usefull to display the original types, eg. generics
// when they are unbound in the current funtion. eg `fun`
let def_fun = &module.ast.find_funtion_definition(&fun_name);
Copy link
Member

Choose a reason for hiding this comment

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

The Names struct already keeps track of generic variable names, so shouldn't this functionality come with using the new printer?

Copy link
Contributor Author

@scristobal scristobal May 12, 2025

Choose a reason for hiding this comment

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

That's what I thought exactly! but the problem is that some types_ are actually Unbound, hence the id of the Generic and the Unbound do not match.

I created a draft PR to reproduce my original approach, using the new Printer and the Names from the current module, like you suggested. Also addded a few dbg! statements, this is the output of the one failed snapshot test:

---- language_server::tests::signature_help::help_for_use_function_shows_next_unlabelled_argument stdout ----
[compiler-core/src/language_server/signature_help.rs:110:5] &module.ast.names = Names {
    local_types: {
        (
            "gleam",
            "Int",
        ) <> "Int",
        (
            "gleam",
            "List",
        ) <> "List",
        (
            "gleam",
            "UtfCodepoint",
        ) <> "UtfCodepoint",
        (
            "gleam",
            "Float",
        ) <> "Float",
        (
            "gleam",
            "BitArray",
        ) <> "BitArray",
        (
            "gleam",
            "String",
        ) <> "String",
        (
            "gleam",
            "Nil",
        ) <> "Nil",
        (
            "gleam",
            "Bool",
        ) <> "Bool",
        (
            "gleam",
            "Result",
        ) <> "Result",
    },
    imported_modules: {},
    type_variables: {
        36: "a",
    },
    local_value_constructors: {
        (
            "gleam",
            "True",
        ) <> "True",
        (
            "gleam",
            "Error",
        ) <> "Error",
        (
            "gleam",
            "Nil",
        ) <> "Nil",
        (
            "gleam",
            "False",
        ) <> "False",
        (
            "gleam",
            "Ok",
        ) <> "Ok",
    },
}
[compiler-core/src/type_/printer.rs:318:9] type_ = Named {
    publicity: Public,
    package: "",
    module: "gleam",
    name: "Bool",
    args: [],
    inferred_variant: None,
}
[compiler-core/src/type_/printer.rs:318:9] type_ = Var {
    type_: RefCell {
        value: Unbound {
            id: 39,
        },
    },
}
[compiler-core/src/type_/printer.rs:318:9] type_ = Fn {
    args: [],
    return_: Var {
        type_: RefCell {
            value: Unbound {
                id: 39,
            },
        },
    },
}
[compiler-core/src/type_/printer.rs:318:9] type_ = Named {
    publicity: Public,
    package: "",
    module: "gleam",
    name: "Float",
    args: [],
    inferred_variant: None,
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Snapshot file: compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__signature_help__help_for_use_function_shows_next_unlabelled_argument.snap
Snapshot: help_for_use_function_shows_next_unlabelled_argument
Source: compiler-core/src/language_server/tests/signature_help.rs:414
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Expression:
pub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 }

pub fn main() {
    use <- guard(b: 1,)
}

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
-old snapshot
+new results
────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    6     6 │
    7     7 │
    8     8 │
    9     9 │ ----- Signature help -----
   10       │-guard(a: Bool, b: a, c: fn() -> a) -> Float
         10 │+guard(a: Bool, b: b, c: fn() -> b) -> Float
   11    11 │       ▔▔▔▔▔▔▔
   12    12 │
   13    13 │ No documentation
────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
To update snapshots run `cargo insta review`
Stopped on the first failure. Run `cargo insta test` to run all snapshots.

thread 'language_server::tests::signature_help::help_for_use_function_shows_next_unlabelled_argument' panicked at /Users/samu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/insta-1.41.1/src/runtime.rs:680:13:
snapshot assertion for 'help_for_use_function_shows_next_unlabelled_argument' failed in line 414

This particular snapshot was confusing, because it also produced guard(a: Bool, b: b, c: fn() -> b) -> Float instead of guard(a: Bool, b: a, c: fn() -> a) -> Float to avoid collision with user-defined generics in pub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 }

or I might totally misused Printer or Names

Copy link
Member

Choose a reason for hiding this comment

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

Ah Ok. I must be misunderstanding something then, because from my knowledge, top-level functions should not be unbound.
Ah! Is it because they are being instantiated so that they can be parameterised? Probably. Maybe we can add those unbound IDs to the Names struct as well?

Copy link
Member

Choose a reason for hiding this comment

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

I think it might be a bug related to this: #2533

Copy link
Member

Choose a reason for hiding this comment

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

No, I don't think so. I managed to work around it in my comment below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you both for jumping in!

@GearsDatapacks
Copy link
Member

I did a bit of experimenting, and I found that we can (mostly) fix this issue by registering unbound type variable IDs when we are instantiating, as demonstrated in this commit.

This also avoids the issue that the current implementation has, which is in cases like the following:

fn identity(x: a) -> a { x }

fn main() {
  let a = Ok(10)
  identity(a)
}

This will print fn identity(a) for signature help, whereas it should print fn identity(Result(Int, a))

@scristobal
Copy link
Contributor Author

I did a bit of experimenting, and I found that we can (mostly) fix this issue by registering unbound type variable IDs when we are instantiating, as demonstrated in this commit.

This also avoids the issue that the current implementation has, which is in cases like the following:

fn identity(x: a) -> a { x }

fn main() {
  let a = Ok(10)
  identity(a)
}

This will print fn identity(a) for signature help, whereas it should print fn identity(Result(Int, a))

I get it! and that is actually a much more simpler solution!

Then if you agree, I will cherry pick your commit over #4589 and continue there.

@GearsDatapacks
Copy link
Member

GearsDatapacks commented May 12, 2025

Sounds good. It might need a bit of tweaking though as it was mostly a quick and dirty proof of concept

@scristobal
Copy link
Contributor Author

fn identity(x: a) -> a { x }

fn main() {
let a = Ok(10)
identity(a)
}

excellent! thank you!

@scristobal scristobal closed this May 12, 2025
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.

3 participants