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

Support multi-span errors and warnings #21

Closed
sezna opened this issue Mar 19, 2021 · 13 comments · Fixed by #296 or #4892
Closed

Support multi-span errors and warnings #21

sezna opened this issue Mar 19, 2021 · 13 comments · Fixed by #296 or #4892
Assignees
Labels
compiler: frontend Everything to do with type checking, control flow analysis, and everything between parsing and IRgen compiler: parser Everything to do with the parser compiler: ui Mostly compiler messages good first issue Good for newcomers P: medium

Comments

@sezna
Copy link
Contributor

sezna commented Mar 19, 2021

Often, you want to display help text or render multiple spans in an error.

@adlerjohn adlerjohn added the compiler General compiler. Should eventually become more specific as the issue is triaged label May 12, 2021
@emilyaherbert
Copy link
Contributor

Can you explain a little more what you mean by this?

@sezna
Copy link
Contributor Author

sezna commented Oct 5, 2021

Yeah, so our error renderer in forc currently can only render one span per error/warning. In Rust, sometimes there are errors that show multiple spans, like, "this value was moved here but used again here ". This issue involves:

  1. Identifying which, if any, issues would benefit from multiple spans (using Rust as inspiration).
  2. Implementing a way for errors/warnings to highlight multiple spans when they're rendered.

@emilyaherbert
Copy link
Contributor

A good one to start might be "not declared as mutable... but used as mutable here ^". What do you think?

@sezna
Copy link
Contributor Author

sezna commented Nov 11, 2021

related: #346

@sezna sezna mentioned this issue Nov 11, 2021
@adlerjohn adlerjohn moved this to Todo in Fuel Network Jan 7, 2022
@emilyaherbert emilyaherbert removed their assignment Apr 5, 2022
@emilyaherbert emilyaherbert added the good first issue Good for newcomers label Apr 5, 2022
@vaivaswatha
Copy link
Contributor

A good one to start might be "not declared as mutable... but used as mutable here ^". What do you think?

Another error that might benefit from this: #751. "Function ^ is being redefined. It was already previously defined ^"

@AlicanC AlicanC self-assigned this Jul 25, 2022
@mohammadfawaz mohammadfawaz added P: medium compiler: frontend Everything to do with type checking, control flow analysis, and everything between parsing and IRgen compiler: parser Everything to do with the parser compiler: ui Mostly compiler messages and removed compiler General compiler. Should eventually become more specific as the issue is triaged labels Jul 27, 2022
@AlicanC AlicanC removed their assignment Aug 8, 2022
@anton-trunov
Copy link
Contributor

One more issue that would benefit from this: #3532. The CEI pattern analysis would highlight just two lines: the one with the external call and the one with storage modification or some other effect.

@mohammadfawaz
Copy link
Contributor

I recall that we tried to do this but then reverted the change for some reason? Is this too difficult to implement? I do find myself needing this often. cc @sezna. Also, if it's too complex, we should probably remove the good first issue tag 😄

@sezna
Copy link
Contributor Author

sezna commented Jan 20, 2023

There was a bug previously where the compiler would panic if the two errors were from different files, which was usually the case, so we reverted the old implementation.

@ironcev
Copy link
Member

ironcev commented Jul 24, 2023

Here is the progress so far and two open questions to agree on. Please provide your feedback on the questions.

Before and after :-)

01A Constant shadowing - Before

01B Constant shadowing - After

Open Questions

Structure of compile messages

What do we want to support in our compile messages and how much freedom, or restriction do we want to offer. For the sake of consistency, I am for a limited, clearly defined number of elements that we can embed in a compile message. This way we can impose consistency across all our messages. So far I support these elements, and enforce them via the compile message (CM) API:

  • CM can have a label.
  • CM must have exactly one (looking for a better term) message that can be an error or a warning. This is what we have as a single error/warning right now.
  • CM can provide additional info. These are references to other places in code related to the core message. They can span over different files.
  • CM can provide help. Help is a plain text. We can provide arbitrary many help strings.
  • Info refer to places in files, whereas the message itself can (and mostly will) but does not have to (we have such situation now).

You can spot all of these elements on the above image.

What do you think of this approach? Do you see any other elements that we could need?

Declarative API

At the moments messages are declared imperatively, pretty much like the way we do warnings right now. This means a lot of clutter and boilerplate. E,g,:

VariableShadowsConstant { name , constant_span} => CompileMessage {
    title: Some("Constants cannot be shadowed".to_string()),
    message: InSourceMessage::error(
          source_engine,
          self.span().clone(),
          format!("Variable \"{name}\" shadows constant with the same name.")
    ),
    in_source_info: vec![InSourceMessage::info(
         source_engine,
         constant_span.clone(),
         format!("Constant \"{name}\" is declared here.")
    )],
    help: vec![
        "Unlike variables, constants cannot be shadowed by other constants or variables, nor they can shadow variables.".to_string()
    ],
    ..Default::default()
},

The proposal is to define our own macros for simplifying the message definition. Something along these lines:

#[label("Constants cannot be shadowed")]
#[error(name.span(), "Variable \"{name}\" shadows constant with the same name.")]
#[info(constant_span, "Constant \"{name}\" is declared here.")]
#[help("Unlike variables, constants cannot be shadowed by other constants or variables, nor they can shadow variables.")]
VariableShadowsConstant { name: Ident, constant_span: Span },

@IGI-111
Copy link
Contributor

IGI-111 commented Jul 24, 2023

I really like this approach. In general having a formal set of items we can use to explain a problem to the user is the right way to go.

I guess if I had to criticize this I would say that it's not very clear at a glance to the compiler dev what a "label" or an "info" is and how different they will look to the user. Ideally I'd want it to be easy to design a decent error without having to look up the spec.
Perhaps we can get close with better naming or something?

Another thing is that this:

#[label("Constants cannot be shadowed")]
#[error(name.span(), "Variable \"{name}\" shadows constant with the same name.")]
#[info(constant_span, "Constant \"{name}\" is declared here.")]
#[help("Unlike variables, constants cannot be shadowed by other constants or variables, nor they can shadow variables.")]
VariableShadowsConstant { name: Ident, constant_span: Span },

is probably expressed as a single proc-macro:

#[error(
    name = name.span(),
    msg = "Variable \"{name}\" shadows constant with the same name.",
    label = "Constants cannot be shadowed",
    info = (constant_span, "Constant \"{name}\" is declared here."),
    help = "Unlike variables, constants cannot be shadowed by other constants or variables, nor they can shadow variables.",
)]
VariableShadowsConstant { name: Ident, constant_span: Span },

One may also want to have more than a single "info", though I would expect the cases where this is necessary are rare.

@ironcev
Copy link
Member

ironcev commented Jul 25, 2023

Completely agree that the terminology is very confusing. I wasn't happy with it either, especially when modeling the corresponding structs in code (CompilerMessage having field message of type InSourceMessage 😱 😱). But that's the first pass and you know how it is - naming things, the most difficult problem in computer science :-)

After pondering about it (a lot) and also partially inspired with the The Anatomy of Error Messages in Rust talk (@IGI-111 thanks for the suggestion) I propose the following:

The whole thing is called Diagnostic and has:

  • level: Error or Warning.
  • reason: Why is this an error/warning? Because... Constants cannot be shadowed. This identifies the whole family of concrete errors/warnings.
  • issue: The issue that triggered the diagnostic. This is a concrete error: Variable "y" shadows constant with the same name.
  • hints: Additional information about the issue. Related lines of code, symbols, etc.
  • help: More insights about the issue. Suggestions how to solve it, etc.

01C Constant shadowing - Terminology

To this, we should also add id (or code) as suggested in #3512. It's the unique identifier of the reason actually. I wouldn't call it error code simply because it can be a warning code as well.

The mandatory fields are:

  • level
  • id (once we introduce it)
  • reason (so far not mandatory for backward compatibility)
  • issue

Everything else is optional. We can have arbitrary number of hints spanning different files and arbitrary number of help lines.

I love the proposal of having a single proc-macro! And it doesn't mean that we cannot have several hints or help lines that way. E.g.:

#[error(
    id = "E01007",
    reason = "Constants cannot be shadowed",
    issue = (name.span(), "Constant \"{name}\" shadows imported constant with the same name."),
    hint = (constant_import_span, "Constant \"{name}\" is imported here."),
    hint = (constant_decl_span, "Constant \"{name}\" is declared here."),
    help = "Unlike variables, constants cannot be shadowed by other constants or variables, nor they can shadow variables.",
    help = "Consider renaming the constant or use an alias for the imported constant.",
)]
ConstantShadowsImportedConstant { name: Ident, constant_import_span: Span, constant_decl_span: Span },

@ironcev
Copy link
Member

ironcev commented Jul 25, 2023

For the record, a very inspiring paper titled Concepts Error Messages for Humans. It emphasize the importance of good (great!) error messages and provides concrete tips how to formulate them. For the purpose of this discussion, everything proposed in the paper can be achieved with the intentionally restricted number of supported elements in Diagnostics, listed above.

The article also mentions the miette crate. Miette is an all-in-one library for defining, collecting, and rendering errors, compatible with anyhow and thiserror. Which interestingly have the #[diagnostic] macro similar to what we discussed:

#[diagnostic(
    code(oops::my::bad),
    url(docsrs),
    help("try doing it better next time?")
)]

(Yes, at glance, it looks like what we did developing our own infrastructure and combining thiserror and annotate-snippets, Miette does out of the box. But more than a glance is needed to support this claim.)

@IGI-111
Copy link
Contributor

IGI-111 commented Jul 26, 2023

The new version looks better, I'm really looking forward to having error ids, if only because once we do we'll be able to provide #[allow()] style annotations.

I do think we made the right choice rolling our own error rendering if only because it gives us flexibility in how we want to tackle the problem, but miette is indeed pretty good and we could certainly take cues out of how it's designed.

ironcev added a commit that referenced this issue Aug 2, 2023
## Description

A new `Diagnostic` type is introduced for detail description of compile
errors and warnings. The change is backward compatible. The existing
`CompileWarning`s and `CompileError`s will continue render as they had
before.

The `Diagnostic` is formed out of a:
- _Level_: Error or Warning.
- _Code_: Unique error or warning code. Placeholder for future. Not used
at the moment.
- _Reason_: Short description of the diagnostic, not related to a
specific error/warning. Answers the question "Why is this an
error/warning?" E.g., Because - "Constants cannot be shadowed".
- _Issue_: Short description of the concrete case that the compiler has
found. E.g., "Variable "X" shadows imported constant with the same name"
- _Hints_: Detailed description of the diagnostic placed in the source
code.
- _Help_: Additional friendly information that helps understanding and
solving the issue, but which is not related to a place in code.

![07C Constant shadowing - Alias - After 02 -
Terminology](https://github.com/FuelLabs/sway/assets/4142833/50c639a1-43f5-4bc2-afa2-5717652f0172)

The `Diagnostic`s are defined imperatively in code right now, pretty
much the sam way we do `CompilWarning`s at the moment. Development of a
proc-macro that should make de definitions declarative is out of scope
of #21.

## Known Limitations

The `annotate-snippets` library has a bug and a missing functionality
for which I opened issues:

- Wrong display of line numbers when using folding of lines of code.
This issue is fixed but there is no patch release provided:
rust-lang/annotate-snippets-rs#52 (comment)
- No possibility to remove the "note:" prefix as shown on the image
above: rust-lang/annotate-snippets-rs#59

These two issues are not blocking. Proposal is to wait for the official
support in the library, or contribute or in the worst case make a
workaround in our code.

## Demo (Before and After)

### Errors
![07A Constant shadowing - Alias -
Before](https://github.com/FuelLabs/sway/assets/4142833/5d67c5e5-d456-4762-b7c7-6fcf7340d6b3)

![07C Constant shadowing - Alias - After
02](https://github.com/FuelLabs/sway/assets/4142833/02691b39-3d08-4776-ba93-5ffeca4546ed)

### Warnings
![06A NonScreamingSnakeCaseConstName -
Before](https://github.com/FuelLabs/sway/assets/4142833/c27279d4-0e6c-4d05-9b00-da7121bb60ad)

![06B NonScreamingSnakeCaseConstName -
After](https://github.com/FuelLabs/sway/assets/4142833/483044f3-f190-4506-9e9e-2d03d70cb3f2)

Closes #21

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
@github-project-automation github-project-automation bot moved this from Todo to Done in Fuel Network Aug 2, 2023
kayagokalp pushed a commit that referenced this issue Aug 3, 2023
@Voxelot @freesig here we go, definitely speeds up the `fuel-core` build
quite a bit locally, and slims the binary size by about a third!

Here are the changes I'm seeing with `fuel-core` `0.10.1`:

**Before**

```
$ ldd ./result/bin/fuel-core

        linux-vdso.so.1 (0x00007ffe3e3eb000)
        libstdc++.so.6 => /nix/store/k2a429wpxgfwp4jaacl9iaqw4kxqjaxa-gcc-11.3.0-lib/lib/libstdc++.so.6 (0x00007f20236ed000)
        libgcc_s.so.1 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libgcc_s.so.1 (0x00007f20236d3000)
        libm.so.6 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libm.so.6 (0x00007f20235f3000)
        libc.so.6 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libc.so.6 (0x00007f20233e9000)
        /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/ld-linux-x86-64.so.2 => /nix/store/bzd91shky9j9d43girrrj6vmqlw7x9m8-glibc-2.35-163/lib64/ld-linux-x86-64.so.2 (0x00007f2024b94000)

$ ls -ahl ./result/bin/fuel-core

-r-xr-xr-x 1 root root  22M Jan  1  1970 fuel-core
```

**After**

```
$ ldd ./result/bin/fuel-core

        linux-vdso.so.1 (0x00007ffe1af53000)
        librocksdb.so.7 => /nix/store/n8fl280p0hdnz9qajwihzsn707jfgkdw-rocksdb-7.4.5/lib/librocksdb.so.7 (0x00007f62e74af000)
        libgcc_s.so.1 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libgcc_s.so.1 (0x00007f62e7495000)
        libm.so.6 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libm.so.6 (0x00007f62e73b5000)
        libc.so.6 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libc.so.6 (0x00007f62e71ab000)
        libsnappy.so.1 => /nix/store/hpmn4gz49wzm5zszg9s24y8sanp29i8c-snappy-1.1.9/lib/libsnappy.so.1 (0x00007f62e719b000)
        libz.so.1 => /nix/store/z18zgvspmxi88ipmk3f3nicvasfq3199-zlib-1.2.12/lib/libz.so.1 (0x00007f62e717d000)
        libbz2.so.1 => /nix/store/1pbdai0n0n16z01nk9yybrll4g2a6mls-bzip2-1.0.8/lib/libbz2.so.1 (0x00007f62e716a000)
        liblz4.so.1 => /nix/store/94ky79f8slvqz77vrc03n228m2xjrvs5-lz4-1.9.3/lib/liblz4.so.1 (0x00007f62e7136000)
        libzstd.so.1 => /nix/store/dq2hx1zvsf8zq1n3b3f7ipzh2kjk7hww-zstd-1.5.2/lib/libzstd.so.1 (0x00007f62e7071000)
        libstdc++.so.6 => /nix/store/k2a429wpxgfwp4jaacl9iaqw4kxqjaxa-gcc-11.3.0-lib/lib/libstdc++.so.6 (0x00007f62e6e58000)
        libpthread.so.0 => /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/libpthread.so.0 (0x00007f62e6e53000)
        /nix/store/7fz6dhhriwv6n4kdg05qi8cwf7mfqza9-glibc-2.35-163/lib/ld-linux-x86-64.so.2 => /nix/store/bzd91shky9j9d43girrrj6vmqlw7x9m8-glibc-2.35-163/lib64/ld-linux-x86-64.so.2 (0x00007f62e8c18000)

$ ls -ahl ./result/bin/fuel-core

-r-xr-xr-x 1 root root  15M Jan  1  1970 fuel-core
```

Note that it seems we still need to keep clang around for bindgen in
order for the rocksdb build script to generate the bindings from its
headers.

Closes #19.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler: frontend Everything to do with type checking, control flow analysis, and everything between parsing and IRgen compiler: parser Everything to do with the parser compiler: ui Mostly compiler messages good first issue Good for newcomers P: medium
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

9 participants