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

Enhanced errors #874

Open
wants to merge 37 commits into
base: development
Choose a base branch
from

Conversation

Thizizmyname
Copy link

This pull request overhauls the visual style of encores error handling with the following functionalities:

  • Use of ANSI escape sequences to colorize error and warning output in a structured format.
  • Enables the ability to provide a hash value based on the type of error given, this can request further information of how and why these kinds of errors occur on a more general basis.
  • Enables the ability for the encore compiler to analyze the environment when an error has occurred and come up with suggestions on how to correct the fault.

Error output and structure

encorenewerror

The ANSI escape sequences are removed it it does not detect a terminal as output, meaning if the output is redirected to a file or to a pipe, it will remove the color sequences and so that the output will remain clean.

Error explanations

If an error occurs that have an explanation it will display a hash-value in which you can provide to the compiler for further information.

$ encorec --explain E002

This will open up a pager with the text explaining a general case where this kind of error can occur.

Code analyzing

In the image we can see an inlined notation by the highlighted error and a longer note underneath. These notation can contain small information on what it expected and what you might wanted to do.
Suggestions available and small list of potential suggestions:

  • expected / found types upon type mismatch.
  • prints method if wrong number of arguments is provided.
  • Suggest function if a non-existing method is called but a function with same made is provided.
  • Exposing a private method if used outside its module (not implemented).
  • Potential candidates to a function- / method-call with a typographical error (not implemented).

Limitations / TODOs

Range error

Due to the way the parser consumes whitespaces before determine end of token, there is a visual bug were the range of each token is the length of the token plus all spaces until the next token.

Error: Left-hand side of operand is not assignable
   --> "plusplus.enc" (Line:35, Column:19)
   |
35 |         (e*2) +=    3)
   |               ^^^^^^ Can only be used on var or fields

Inefficient Suggest analyzation

The result for the inlined suggestion as well as the longer underneath are handled by two separate functions, making it not possible to take advantage of the ability of determining after the fact what kind of message should be displayed where, if these were produced as one function it would be more efficient to not needing to re-evaluate values as well as determining the position based on the size or complexity of the result.

Thizizmyname and others added 30 commits May 25, 2018 13:43
- Only shows the new error on NonAssignableLHSError
- Only displays the first line of the errored code, if the
expression/etc is segmented on multiple lines it will for now not show
the entire part
Extracted the printer to own file so that TypeError only holds the data
structures and text for different error descriptions/suggestions.
Also removed all deprecated code.
This is due to the visual bugs in prettyprinter.hs introduced by the differences on how the libraries handle empty documents as well as differences with functions such as nest and hang.
Need to expose the annotated version of Doc in prettyprinter to apply prettyprinted text in suggestions
Copy link
Contributor

@kikofernandez kikofernandez left a comment

Choose a reason for hiding this comment

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

I really like this PR. The code is quite understandable and reading the error messages is much much better than what we had in place before. I have added refactorings, suggestions and spotted a few places that I think need a bit of a clarification, before we merge this.

One thing that I do not know if it is too difficult, would be to basically compile an Encore program with the explanation flag and, if there are any errors, show not only the place and backtrace of the error, but also the suggestion that matches the appropriate code. This way, if I get an error, I do not have to chase the error / warning code to get the good explanation.

As I said, really good PR. Looking forward to the changes :)

src/types/Typechecker/ExplainTable.hs Show resolved Hide resolved
showPosition :: Position -> Doc TCStyle
showPosition pos = styleLogistic (text "-->") <+> (text $ show $ pos)

codeViewer_ver1 :: (TCType a, Suggestable a) => a -> [String] -> Doc TCStyle
Copy link
Contributor

Choose a reason for hiding this comment

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

which of the codeViewers is the right one?


pipe = char '|'

declareError :: Error -> Doc TCStyle
Copy link
Contributor

Choose a reason for hiding this comment

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

The declareError and declareWarning are pretty much the same except for the hash format and the word "Error" or "Warning". This fact make it good for a refactoring. I would recommend:

  1. Create in ExplainTable.hs the following class and instances and remove the methods lookupHash and lookupHashW:
    class Explanainable a where
        explain :: a -> Maybe Int
    
    instance Explanainable Error where
        explain err = toKey err
    
    instance Explanainable Warning where
        explain warn = toKeyW warn
    

This basically allows you to have ad-hoc polymorphism over the types, which is what you want. We want to be able to call the method explain without having to call one specific method for errors and another one for warnings.

  1. Update the functions declareError and declareWarning to the following ones:
declareError :: Error -> Doc TCStyle
declareError = printText "[E%04d]" "Error" . explain

declareWarning :: Warning -> Doc TCStyle
declareWarning = printText "[W%04d]" "Warning" . explain

printText _ _ Nothing = empty
printText format msg (Just num) = let hash = text $ printf format num
                                  in styleClassify $ text msg <> hash <> char ':'

In my personal opinion, this is more understandable than the function before and it hides internal facts that are not needed for the functions declareWarning and declareError, such as that there is an option type.

Copy link
Author

Choose a reason for hiding this comment

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

  1. I like it! it has been changed.

  2. This solution would not produce the correct result. it need to do:
    styleClassify $ text msg ______ <> char ':'
    Regardless of the Maybe-type. And I like to preserve not having two lines that has to be the same. I have reformatted the function a little hoping to convey that more properly.

@@ -14,6 +14,9 @@ import System.Directory
import System.IO
import System.Exit
import System.Process
import System.Posix.Process
Copy link
Contributor

Choose a reason for hiding this comment

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

would it be possible to import only the things that you are using?
Unless you really require a lot of functions from the modules imported here.

Basically, I do not know what combinator from System.Posix.Process, System.Pager, System.Console.ANSI you are using.

src/front/TopLevel.hs Show resolved Hide resolved
makeNotation :: Doc TCStyle
makeNotation = styleLogistic (pipe $+$ equals) <+> styleDesc (text "note:")

-- How to determine if to use a smallSuggest or longSuggest:
Copy link
Contributor

Choose a reason for hiding this comment

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

Since it seems that we can choose between small and long suggestions, can we choose one version from the version1 or version2 and change the small or long suggestions as we see fit? Maybe adding some documentation at the beginning of the module can clear things up.

Copy link
Author

Choose a reason for hiding this comment

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

I could definitely add some documentation.

What do you mean version1 or version2? the codeviewer?
There are two versions right now doing the same thing, one is ment to be removed and amended before merging.
It's made so that you could construct different codeviewers and choose based on the error
(an example is if you have the wrong arguments for a function, the code viewer could display the actual function declaration as well as where the programmer wrote wrong. I think the declaration s range also includes the entire body which is why I never made such a viewer)

The small and long suggest will always print, which is why default is empty (nothing will print).
A desire that I included as future work was to refactor the suggestions to be returned as one function, meaning that you could evaluate the code, and depending on how much information you want to relay back you could then choose if it would be displayed as a short or long suggest (or both). Right now you would need to compute this for both functions individually.

src/types/Typechecker/Typechecker.hs Outdated Show resolved Hide resolved


printError :: TCError -> IO ()
printError err@(TCError _ Env{bt = []}) =
Copy link
Contributor

Choose a reason for hiding this comment

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

the TCError ADT was updated from TCError Error Backtrace to TCError Error Env. However, I believe that you never make use of the environment when you try to print an error. I was wondering if you really need the environment in the TCError. I am sure I am wrong, but I could not find where do you make use of the Env from the TCError.

Copy link
Author

Choose a reason for hiding this comment

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

The Env is passed along to the long and short suggest. Otherwise we could not evaluate an error and give suggestions.

Copy link
Contributor

Choose a reason for hiding this comment

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

right! I just saw that you need the environment for a specific case of the long suggest. The short suggest doesn't seem to use it

Copy link
Author

Choose a reason for hiding this comment

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

short suggest uses it as well to figure out if the name of an unknown method is available as a function. (As I would like to refactor this code into a single function returning both the possible short and long suggestions it would be wise to leave the arguments the same)

| isMethodNameAFunction name ty env = text $ printf "Did you mean function `%s`?" (show name)
smallSuggest _ = empty

longSuggest (TCError (TypeMismatchError actual expected) _) =
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe the first two cases of longSuggest can be refactored using guards. They are an identical copy except from the error type/

Copy link
Author

Choose a reason for hiding this comment

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

As a TypeWithCapabilityMismatchError has a very "ugly" description I think it might be more wise to remove it entirely from suggestible as a way of subtly informing that this error needs rework.




isMethodNameAFunction name ty env =
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest to put this method inside a where clause in the place where it is used, since it does not seem to be a combinator that is needed for multiple functions.

Copy link
Author

Choose a reason for hiding this comment

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

I wanted these abstracted helper-functions to not be local so that it could be used by anything, specially as long as short and long-suggest are two different functions.

A desire that I included as future work was to refactor the suggestions to be returned as one function [...] Right now you would need to compute this for both functions individually.

@kikofernandez
Copy link
Contributor

Before merging, I would like to see if the other comments are addressed (or there is a reply).
@Thizizmyname As soon as you address the remaining comments and fix the current conflicts, I can merge this. Thanks!

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.

None yet

2 participants