Return type oriented refactor of the "error store" concept#1
Draft
Return type oriented refactor of the "error store" concept#1
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note that this PR is to my own branch and not upstream. Generally maintainers don't like refactoring PRs, I did this work for my own exploration. If it's useful to the maintainers, great! But it's more of a proof of concept.
What daft does today
Proc-macros should be friends with rust-analyzer source: Oxide and Friends episode about daft.
The way this "niceness" is implemented in daft is that an error accumulator is passed into every function that could error. Then at the end, if there's stuff in that accumulator emit code (if any could be generated) and also emit the errors.
The return types in this situation are either
Tif errors would not prevent code generation orOption<T>if they could.Edit (Mar 27th): There are also cases where
Noneis a valid return option in the case of something being ignored/hidden. Because the pattern is to check the presence oferrors.has_error()it's difficult to distinguish between "None" was returned because it's marked as ignored, but there's a minor error (like duplicate attributes) versus "We could not generate T so return None, and there are major errors, we should not continue."What this PR does
This PR relies on syn's built-in ability to accumulate errors syn::Error::combine() and moves to clarify possible return state by updating the return types. The unit of accumulation is now local to functions and the return types tell us about the effect of that function on compilation:
Result<T, syn::Error>(T, Option<syn::Error>)Result<T, Option<syn::Error>, syn::Error>.Ok(T, None)indicates no errorsOk(T, Some())indicates an error that did not block code generation.Err()indicates code could not generate due to errorTo put it another way: A return of
Option<syn::Error>indicates a "warning" error whileResult<T, syn::Error>indicates an unrecoverable error.There's still a convenience error accumulator, but it's a very thin wrapper around
VecDeque<syn::Error>.Thoughs
A big upside of this approach (in general) is that it allows error accumulation when the signature is not under direct programmer control. One example is someone implementing
syn::parse::Parsewhich has a signature offn parse(input: ParseStream<'_>) -> Result<Self>;. I feel it would be easier to apply this pattern to an existing proc-macro codebase.The big downside of my approach is that error accumulation when the return type is
Result<T, syn::Error>becomes social. Someone could use a?out of habit when they should be collecting and emitting.What I want
I want to talk about patterns and best practices for error accumulation in proc-macros. Daft presented one paradigm, and this PR presented another. I'm curious what people think (pros and cons of each) and if there are other patterns that might be even better! Probably the best way to have those conversations is on mastodon https://ruby.social/@schneems. Comments below a PR make for horrible conversation/exploration threads.
There's also probably a better pattern for the inside of
DiffFields::newthat I refactored, LMK if you've got something with the same behavior but more concise.Terms and conditions
Commits are mostly checkpoints where code compiled, if I were to do this "properly" I would probably go back and re-do the work in a more systematic fashion. This was "change some code, get it to compile, commit" with little thought for consistency.