I understand at first glance this proposal might not seem necessary. I wanted to give some insight into how I got here. I'd like to outline a couple of the alternatives I tried and some of the issues I ran into while doing so.
As a reminder: the goal is to emit multiple error diagnostics during the compilation step and fail the compilation. *Let's stick with `raise/1`* Everyone is already using raise to emit errors from macros. The compiler diagnostic that is emitted from raise is missing a position but it does have a stacktrace. While the original frame in that trace does point to your macro, it probably still isn't the correct line. Take the following pseudocode. raise will cause a stacktrace that points to `query` on line 1 when your actual error is on line 3. Currently, raise does not and cannot know exactly where your error is. ``` 1. query Thing do 2. id 3. error_here 4. end ``` *So add `raise/2`* Maybe we enhance raise so it takes an optional stacktrace as a second argument like IO.warn does. While I think this is a great idea, it doesn't meet one of the two criteria above (emit multiple errors). For the record though, I do think there are cases out there where rather than let your macro get into an inconsistent state you would want to raise an error and stop compilation. If we allowed a custom stacktrace to be passed in to raise then the error diagnostic it emitted would be more useful. *Why not use IO.warn/2 with the `warnings_as_errors` flag?* This solution does indeed solve both my criteria: multiple errors are emitted and the build fails. But the developer experience is not ideal: - I am forcing my users to add a compiler flag to their project. It's one more thing to remember when using my library. - As a macro author I would like to emit both warnings and errors. If I can only emit warnings (which are treated as errors) then I am unable to distinguish between the two. - This forces ALL warnings in your project to be treated as errors which may not be desirable in some cases. *How about IO.warn/2 and return a raise if you hit any warnings?* So let's imagine then that I use IO.warn to report all of my errors. If I had to report any errors then I'll make my macro output an AST for `raise "broken macro: check the logs"`. I don't have to force the `warnings_as_errors` flag on my users this way and I am able to emit multiple errors. But now the compilation is successful. I have to rely on my users actually exercising their code or having a good test suite to find out that the output of the macro isn't actually going to work. On Monday, May 4, 2020 at 5:37:14 PM UTC-6, Dallin Osmun wrote: > > I propose that we add `IO.error/2` which matches the signature of > `IO.warn/2`. It emits error diagnostics/messages instead of warnings and it > causes the compile step to finish with :error. > > > *Reasoning* > > Often when building a macro you'll do some validation and `raise` an error > if something isn't right. This creates a poor experience for the person > using the macro for two reasons: > 1. You can only raise one error at a time > 2. You don't get a good picture of where the error is because your whole > editor turns red > > You can solve both of those problems by using `IO.warn/2`. You can emit > multiple warnings in a single compile step and you can pass in a stacktrace > which gets turned into a Compiler Diagnostic that in turn, creates good > editor hints. But now the compilation succeeds and you're left in a bad > state. > > I think it is useful to see multiple errors at a time because it shortens > the feedback loop. It also gives more context and can help you realize > where the root cause of your issue lies. > > I think it would be useful to have a function that shared the properties > of `IO.warn/2` and `raise/1`: > - I can emit multiple messages during a single compilation run > - These messages are output as Compiler Diagnostic errors > - Invoking this function will ultimately cause the compilation step to > result in an :error > > > *Examples* > > Today in Ecto Query, this snippet will cause the entire file to turn red > because we mistakenly used sort_by instead of order_by. > query p in Product, sort_by: p.price > > Lets assume we forgot to load the :user type in this Absinthe Schema. We > have two errors but only one gets displayed. Once again, the entire editor > turns red. If instead we saw each line that referenced :user turn red it > might remind us more quickly that we forgot to call `import_types > MyApp.UserTypes`. > query name: "Query" do > field :user, :user, resolve: &MyApp.Users.get_user/3 > ... > field :users, list_of(:user), resolve: &MyApp.Users.list_user/3 > ... > end > > I'm currently working on a library to create GraphQL queries. I can detect > three errors in the following snippet. Today I can only report one at a > time and am greeted with the wall of red. If I switch to `IO.warn/2` I can > report them all and see nice editor hints but my user is left with broken > code after compile. > query do > user(id: "should_be_a_number") do # wrong type for id > firstNam # misspelled, should be firstName > lastName do # lastName should not have a do...end block > name > end > end > end > > > *Code* > > Here is a patch that adds `IO.error/2`: > https://github.com/elixir-lang/elixir/compare/master...numso:io-error > > -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/5a4adeea-b391-4edb-8d89-cdd80492a517%40googlegroups.com.