Skip to content
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

Annotation based dropdowns not working for static methods on built-ins. #6715

Closed
jdunkerley opened this issue May 16, 2023 · 8 comments · Fixed by #6878
Closed

Annotation based dropdowns not working for static methods on built-ins. #6715

jdunkerley opened this issue May 16, 2023 · 8 comments · Fixed by #6878
Assignees
Labels
--bug Type: bug p-high Should be completed in the next sprint

Comments

@jdunkerley
Copy link
Member

jdunkerley commented May 16, 2023

image

Both encoding parameters should have the same dropdown (correctly shown for "Hello World").

The parameters are listed correctly and the type based dropdown is shown but for the static method on the built-in the get_widget_json is not called.

@Frizi
Copy link
Contributor

Frizi commented May 16, 2023

Looks to be a GUI issue - the widget data is not requested for static methods. If the metadata is queried manually, it is reported correctly.

@Frizi
Copy link
Contributor

Frizi commented May 16, 2023

Dug a little deeper and discovered the root cause. The expression call is properly parsed and recognized. Span tree looks like this:

▷Text.from_bytes [13]◁Root ext_id=1b02c13f-939a-4207-aecb-3ea15227ad3a
▷Text.from_bytes [13]◁ ├─Chained ext_id=1b02c13f-939a-4207-aecb-3ea15227ad3a
▷Text.from_bytes [13]◁ │  ├─Chained ast_id=1b02c13f-939a-4207-aecb-3ea15227ad3a
▷Text.from_bytes◁      │  │  ├─Operation ast_id=595a78b6-6895-4e93-b726-1e1dcf6bec2b
▷◁                     │  │  │  ├─InsertionPoint(BeforeArgument(0))
▷Text◁                 │  │  │  ├─Argument name="self" call_id=1b02c13f-939a-4207-aecb-3ea15227ad3a ast_id=297131fe-f5d2-4233-9b8e-89bb987211c5
    ▷.◁                │  │  │  ├─Operation ast_id=7a62a262-f424-4c03-81e5-5d7f00c08807
     ▷◁                │  │  │  ├─InsertionPoint(BeforeArgument(1))
     ▷from_bytes◁      │  │  │  ├─Argument ast_id=e59b521c-0cf9-4c50-a779-18e2d8f2ffa0
               ▷◁      │  │  │  ╰─InsertionPoint(Append)
                ▷[13]◁ │  │  ╰─Argument name="bytes" call_id=1b02c13f-939a-4207-aecb-3ea15227ad3a ast_id=d1e97858-a4e1-4ca1-9277-43d8164dcbc1
                ▷[◁    │  │     ├─Token
                 ▷◁    │  │     ├─InsertionPoint(BeforeArgument(1))
                 ▷13◁  │  │     ├─Argument ast_id=527498e0-2a2e-406a-916c-66ea3d88f480
                   ▷◁  │  │     ├─InsertionPoint(Append)
                   ▷]◁ │  │     ╰─Token
                    ▷◁ │  ╰─InsertionPoint(ExpectedArgument { index: 1, named: false }) name="encoding" call_id=1b02c13f-939a-4207-aecb-3ea15227ad3a
                    ▷◁ ╰─InsertionPoint(ExpectedArgument { index: 2, named: true }) name="on_problems" call_id=1b02c13f-939a-4207-aecb-3ea15227ad3a

The call and its target is properly recognized:

CallInfoMap { call_info: {1b02c13f-939a-4207-aecb-3ea15227ad3a: CallInfo { target_id: Some(297131fe-f5d2-4233-9b8e-89bb987211c5) }} }

Then, the IDE asks for the expression info of this call ID and receives one:

{
    "expressionId": "1b02c13f-939a-4207-aecb-3ea15227ad3a",
    "type": "Standard.Base.Function.Function",
    "methodPointer": {
        "module": "Standard.Base.Data.Text.Extensions",
        "definedOnType": "Standard.Base.Data.Text.Text.type",
        "name": "from_bytes"
    },
    ...
},

The methodPointer is then used to query suggestion database. Unfortunately, no entry is found. The method is in the suggestion database, but under a different key. This is the entry that we were SUPPOSED to find:

{
  "type": "Add",
  "id": 93,
  "suggestion": {
    "type": "method",
    "module": "Standard.Base.Data.Text.Extensions",
    "name": "from_bytes",
    "selfType": "Standard.Base.Data.Text.Text",
    ...
  }
},

Note that methodPointer.definedOnType does not match suggestion.selfType. The trailing .type causes the lookup to fail.

I'm not sure if this is the IDE issue and we should treat the .type specially, or if it's the language server that is inconsistent here.

@Frizi
Copy link
Contributor

Frizi commented May 16, 2023

It turns out in some parts of IDE we already strip that postfix, and in fact rely on it to inform us about how to interpret that method call's arguments. Removing it on the language server side would break that logic.

// When the entry was not resolved but the `defined_on_type` has a `.type` suffix,
// try resolving it again with the suffix stripped. This indicates that a method was
// called on type, either because it is a static method, or because it uses qualified
// method syntax.
maybe_entry.or_else(|| {
let defined_on_type = method_call.defined_on_type.strip_suffix(".type")?.to_owned();
let method_call = MethodPointer { defined_on_type, ..method_call.clone() };
let entry = suggestion_db.lookup_by_method_pointer(&method_call)?;
let invocation_info = entry.invocation_info(&suggestion_db, &self.parser());
Some(invocation_info.with_called_on_type(true))
})

In order to fix this, we have to use the same resolution strategy for widget related method queries to the suggestion database.

@farmaazon
Copy link
Contributor

There are more places to fix, unfortunately, as - apparently - only you, @Frizi, have been aware of the .type suffix so far (the snippet you posted is also written by you, according to blame). So I think no other part of IDE actually expects .type suffix.

And another problem is that Language Server seems to be not consistent in adding .type suffix, as MethodPointers and expression type updates include them, but the suggestion database does not (relying on is_static flag). The search/complete also seems unaware of .type suffix, as it does not propose static methods of given type.

Of course, we can apply a set of patches, but I think those inconsistencies should be resolved in the long run.

@Frizi
Copy link
Contributor

Frizi commented May 17, 2023

Yeah, I remember writing that now, though It took me a while to bring that to mind at first. The .type suffix is specifically added to definedOnType when the method target is a whole type, as opposed to a value of given type. This is not unique to statics, and it influences the interpretation of passed arguments. So I don't really consider that a language-server inconsistency, but rather a defficiency of our suggestion database lookup method.

expression static .type suffix in definedOnType self argument widget data visualization target
Text.from_bytes [13] none Text
" a ".trim " a " " a "
Text.trim " a " " a " " a "
text3.trim " a " 🤷 🤷 depends on text3 type " a " or text3 " a " or text3

The last example is actually quite important, as it would be impossible to properly resolve its widgets without having the .type suffix present in the method pointer. Notice that on the screenshot below the dropdown is in correct place in all cases, even though the two rightmost nodes use a variable method target of different type, but eventually resolve to the same method.
image

The issue is that this full resolution is only used for static dropdowns. Dynamic dropdowns are failing to resolve, because the method lookup is not done correctly for calls on type.

@farmaazon
Copy link
Contributor

farmaazon commented May 17, 2023

Our suggestion database reflects exactly what language server send us, it only removes unnecessary Main module names from paths.

The MethodPointer responsibility is to point to method definition. Its primary use is to tell as where is the definition of the node if we would want to enter it.

Now you see (I hope) the problem here: the definition pointer should not depend on the call syntax! This is why the field is called definedOnType, not selfArgumentType.

(The last two paragraph cc @JaroslavTulach @4e6)

In your case, I think you're taking the information from the wrong place. If you want to know what is the type of self argument, you should just read the type of the proper expression ID (in the last case on your screenshot, it could be type of text2/text3 expression in the node).

@JaroslavTulach
Copy link
Member

JaroslavTulach commented May 17, 2023

The .type suffix is specifically added to definedOnType when the method target is a whole type, as opposed to a value of given type

Looking from Enso language perspective:

enso$ ./built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/bin/enso --repl
> Text.from_bytes
>>> Text.type.from_bytes[Extensions.enso:675-2395]

e.g. the type appears in the output as well. However that is the "runtime value" - when we take a look at static source code, then there is no .type at all.

tell as where is the definition of the node if we would want to enter it.

If you are interested in "static information" - e.g. where a source code is - then we may want to modify the MethodPointer and expression type updates to avoid using .type completely. That would mimic the source structure more.

@farmaazon farmaazon added the p-high Should be completed in the next sprint label May 19, 2023
@farmaazon farmaazon assigned Frizi and unassigned farmaazon May 19, 2023
@farmaazon farmaazon added this to the Design Partners milestone May 19, 2023
@farmaazon farmaazon moved this from ❓New to 📤 Backlog in Issues Board May 19, 2023
@farmaazon
Copy link
Contributor

farmaazon commented May 22, 2023

The discussion continues https://github.com/orgs/enso-org/discussions/6762

For now, we will make another place where we just check type for .type suffix`.

GitHub
When I was discussion with @Frizi the issue #6715, we discovered several points about Language Server API to be discussed broader @4e6 @hubertp : What widgets needs to know The widgets need to know...

@mergify mergify bot closed this as completed in #6878 May 30, 2023
mergify bot pushed a commit that referenced this issue May 30, 2023
Implements #6792
Fixes #6715
Fixes #6052
Fixes #5689

The dynamic dropdown widgets entries now can specify additional widget configuration as a list of `parameters` of the inner method call. That allows for creating smarter widgets within nested constructors, taking the outer widget's context into account.

<img width="772" alt="image" src="https://github.com/enso-org/enso/assets/919491/97c70654-9170-4cf0-ae4d-2c25c74caa96">

With the changes to the serialization logic, I have also adressed issues related to automatic label generation for both static and dynamic dropdown entries. For access chains (e.g. `Foo.Bar.Baz_Qux`), the label will now always contain only the last segment, and all underscores will be removed (e.g. `Baz Qux`). This also applies to dynamic entries where the label is not explicitly specified in method annotation.

<img width="265" alt="image" src="https://github.com/enso-org/enso/assets/919491/1abe6c77-010b-4622-b252-97cd1543cb48">

Additionaly, now the dynamic entries containing constructors will also be resolved within suggestion database, allowing us to automatically insert relevant import, shorten the actually used expression and wrap it with parentheses if required. That was required for nested widgets to show up, as we depend on properly resolved argument names to show them. The widget definitions in annotations no longer need to wrap the expressions manually. Instead, the constructors used in dropdown entries should be specified using fully qualified names, similarly to how we do it in tag values.

CC @jdunkerley - The dropdown entries containing just a constructor will no longer need added parentheses around them. Instead, the constructors should be specified using fully qualified names, similarly to how we do it in tag values.

<img width="389" alt="image" src="https://github.com/enso-org/enso/assets/919491/19944b5b-d0c7-43ac-bf17-ca1556e0b3f0">

Note that currently the import resolution is attempted even if the used constructor is is not specified using a fully qualified name. To accomplish that, the IDE is performing a more expensive search through whole suggestion database for matching type and module (e.g. in example above, we are searching for a match for `Aggregate_Column.First`). If there are multiple potential matches due to a name collision, it is undefined which one would be preferred. Effectively one will be picked at random. To avoid that, the libraries should over time transition to using fully qualified names wherever possible.

# Important Notes
I have removed the `payload` field from the span tree, and with it the generic argument on its nodes. This was already partially done on the branch with new design, on which I also had a few changes that turned out to be useful for this PR. So I pulled it in as well. It is a nice simplification that will ease our further work on removing the span-tree altogether. The biggest impact it had was on the node output port, where I had to store the port data outside of the span tree. This is the approach we would be taking when transitioning to AST anyway.
@github-project-automation github-project-automation bot moved this from 📤 Backlog to 🟢 Accepted in Issues Board May 30, 2023
@farmaazon farmaazon moved this from 🟢 Accepted to 🗄️ Archived in Issues Board Jun 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
--bug Type: bug p-high Should be completed in the next sprint
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants