-
Notifications
You must be signed in to change notification settings - Fork 715
feat: Split analysis-only and runtime-only CheckErrors #6626
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
base: aac-client-breaking
Are you sure you want to change the base?
feat: Split analysis-only and runtime-only CheckErrors #6626
Conversation
| /// - Failures based on runtime arguments or state changes. | ||
| /// - Value-level type mismatches. | ||
| #[derive(Debug, PartialEq)] | ||
| pub enum CheckErrorKind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still working through but I think I might rename CheckErrorKind to RuntimeCheckErrorKind as I have forgot a few times whether CheckErrorKind was runtime or static specific. Plus then its more consistent with the naming convention used for the other *CheckErrorKind. I would expect CommonCheckErrorKind to be renamed CheckErrorKind if anything but I am okay with it being called CommonCheckErrorKind to be extra explicit.
|
I think this change is a net improvement and it does actually make it easier for me to understand what is happening at what point in the code base. I am not sure yet what a better architectural pattern might be available, but even so I think its still an improvement even if the public API is not super clean. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This split definitely clarifies a lot, making the purpose of each error variant much clearer.
On the other hand, the side effects introduced by the shared variants don’t seem to have a clean solution. I agree that the alternatives you listed as “ugly” would likely make the code worse, so I wouldn’t pursue those either.
If we decide to stick with this architecture, I think we’ll have to accept that the common enum remains public. Perhaps we could give it a name that better reflects its role (not complitely sure about that), especially if we manage to separate the cost-related parts into their own enum (something I’ll try on the ParserErrorKind side).
The only alternative I see would be to revert to a single CheckErrorsKind enum, but keep the improvements you’ve made in documenting properly each error variant. If we can also find a way to merge the “expect-like” ones, we could also reduce the total count.
About the expect-like merge: the problem would be to be sure to preserve the same consensus behaviour (even if some unreachable). So as a first step, could we think to merge them in two different expect variants? One for the expect we are sure are rejectable and one for the one we are not, with a string the explain the kind of error (or a sub enum eventually).
|
@jacinta-stacks @federico-stacks Thanks both for the inputs! re: Stick with a single re: preserve same consensus behavior then before. Definitely makes sense. Actually, there already is such a variant |
Yes, I believe so. I think the split makes it much more clear where different errors can surface, and what is actually semantically possible at different phases of the interpreter. It makes the code more explicit, and makes writing new error variants safer: if you add a new CheckErrors today, the rust compiler won't necessarily highlight that handlers for wrapped versions of that variant need to be updated as well.
I think you've basically found the major options. Of the options, I like the generic option the best, however, if we're implementing pub fn new_list(
entry_type: TypeSignature,
max_len: u32
) -> Result<ListTypeData, CommonCheckError> {
// implementation
}Then most callers would be able to just use
I think it's fine (and maybe even good?) to have all these different error types in the Public API.
I agree with this second phase too, so long as we go with the second option there (a new variant that accepts the transaction but marks it as failed). While we should be able to identify that some of these are unreachable without bypassing the analysis check, actually guaranteeing that this is the case is pretty challenging. |
Description
Some input would be appreciated!
StaticCheckErrorKind: 98 total variantsCheckErrorKind: 87 total variantsCommonCheckErrorKind): 26StaticCheckErrorKind: 34CheckErrorKind: 22Why Separating the two?
Separating static from "execution-time" Check errors would make the code explicit about what the contract analysis guarantees vs what can only be checked at runtime. Currently, all errors seem possible at any time.
More importantly, this split enables a second phase of cleanup: many
CheckErrorKindat runtime are actually safeguards for unreachable code that's already protected by contract analysis. Even the relative tests bypass the contract_analysis to stimulate the unreachable code paths. Once separated, we can identify these and replace them withCheckError::Expects(or a new variant that accepts the transaction but marks it as failed for consensus safety).We currently have 79
CheckErrorKind variants(58 shared with static). After this cleanup, we'd likely reduce this to ~40 actual runtime errors, making the codebase significantly clearer about what can actually fail at runtime.WIP: currently the
CommonCheckErrorKindenum is public, but should become private and invisible to users of theclarity-typesandclaritycrates (can't be truly private because theclaritycrate also needs it. However, this is solvable by making it public but ensuring all public functions only returnStaticCheckErrororCheckError)While trying to achieve this clean public API, I've tried multiple approaches, but each has significant problems (uuugly):
Duplicate public functions
Problems:
clartyhas to return aCommonCheckErrorand use a function inclarity-types)Generic error type
Problems:
ListTypeData :::new_list::<CheckError>(...).unwrap()Open questions:
Looking for feedback on whether to push through with a better approach, or abandon this split entirely.
Applicable issues
Additional info (benefits, drawbacks, caveats)
Checklist
docs/rpc/openapi.yamlandrpc-endpoints.mdfor v2 endpoints,event-dispatcher.mdfor new events)clarity-benchmarkingrepo