-
Notifications
You must be signed in to change notification settings - Fork 224
Implement AnyCalendar using a macro that improves robustness #7071
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: main
Are you sure you want to change the base?
Conversation
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. |
In the other thread @robertbastian said
I've seen you tweak the datetime crate and what would be a 100-line bug-prone change becomes a 3-line change with the macro. I used to dislike complex macros, too, but the extra time it takes for a developer to learn the macro is time saved in code review and bug reports. |
This particular macro is not nearly as complex as the datetime macro, and based on @Manishearth's consistent nudging to document internal APIs, I think it's not hard to learn to use, either. The macro could be made substantially simpler if Rust ever allows macros to generate match arms. |
/gemini review, wdyt? |
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.
Pull Request Overview
This PR refactors the AnyCalendar implementation by introducing a generalized macro system that improves code maintainability and robustness. The change replaces repetitive manual match statements with a flexible macro-based approach that reduces duplication and potential errors.
- Replaces manual match statements with a generalized
match_cal_general
macro system - Introduces helper macros (
pattern_cal
,pattern_cal_date
,pattern_cal_date_date
) for pattern matching - Removes unused
DEBUG_NAME
constants and related debug functionality
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
components/calendar/src/any_calendar.rs | Implements new macro system, refactors Calendar trait methods to use macros, removes debug helper functions |
components/calendar/src/cal/japanese.rs | Removes unused DEBUG_NAME constants for Japanese and JapaneseExtended calendars |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
None | ||
); | ||
match result { | ||
Some(Ok(result)) => Ok(result), |
Copilot
AI
Oct 13, 2025
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.
The pattern matching on Some(Err(_))
is missing, which could cause a panic if the until
method returns an error. Add a case to handle Some(Err(e)) => Err(e.into())
.
Some(Ok(result)) => Ok(result), | |
Some(Ok(result)) => Ok(result), | |
Some(Err(e)) => Err(e.into()), |
Copilot uses AI. Check for mistakes.
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.
no copilot, the Err variant is uninhabited
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.
Code Review
This pull request introduces a powerful new macro, match_cal_general!
, to significantly reduce boilerplate and improve the robustness of AnyCalendar
's implementation. The refactoring is a great improvement, making the code more concise and maintainable.
I've found a critical issue that will prevent compilation due to a non-exhaustive match in the until
method. I've also identified a couple of medium-severity maintainability issues: an unclear identifier in one of the new macros and some inaccuracies in the documentation for match_cal_general!
.
Once these issues are addressed, this will be a solid improvement to the codebase.
match result { | ||
Some(Ok(result)) => Ok(result), | ||
None => Err(AnyCalendarDifferenceError::MismatchedCalendars), | ||
} |
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 match
statement is not exhaustive. The result
variable has the type Option<Result<types::DateDuration, Infallible>>
, but there is no arm to handle the Some(Err(_))
case. While the error type is Infallible
, meaning this arm is unreachable, the compiler requires it to be handled explicitly. This will cause a compilation error.
You can add an arm to handle this case, for example by matching on the Infallible
error.
match result {
Some(Ok(result)) => Ok(result),
Some(Err(never)) => match never {},
None => Err(AnyCalendarDifferenceError::MismatchedCalendars),
}
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.
No Gemini, the Err variant is uninhabited. The code wouldn't compile otherwise.
/// 2. `$pattern`: The name of a macro that generates a pattern for the match statement. It is passed as arguments: | ||
/// - `$cal` | ||
/// - An ident to be used for matching the AnyCalendar, `c`. | ||
/// - A list of idents to be used for matching AnyDateInners and the Hijri tabular algorithm | ||
/// 3. `$expr`: The name of a macro that generates an expression for the match statement. It is passed the same | ||
/// arguments as `$pattern`, except without the Hijri tabular algorithm, since it is contained within `c`. |
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.
The documentation for parameters 2 and 3 of the match_cal_general!
macro seems to be incorrect. It states that the first argument to the $pattern
and $expr
macros is $cal
, but it's actually the calendar variant identifier (e.g., Buddhist
).
Additionally, the description for parameter 3 is a bit confusing. It could be clarified that the $expr
macro receives the same arguments as $pattern
but without the algorithm identifiers for HijriTabular
.
Updating the documentation would improve clarity for future maintainers of this complex but powerful macro.
/// 2. `$pattern`: The name of a macro that generates a pattern for the match statement. It is passed as arguments:
/// - The calendar variant ident (e.g. `Buddhist`).
/// - An ident to be used for matching the `AnyCalendar` variant, `c`.
/// - A list of identifiers for `AnyDateInner` variants. For `HijriTabular`, this list also includes identifiers for the algorithm.
/// 3. `$expr`: The name of a macro that generates an expression for the match statement. It is passed as arguments:
/// - The calendar variant ident (e.g. `Buddhist`).
/// - An ident for the `AnyCalendar` variant, `c`.
/// - A list of identifiers for `AnyDateInner` variants.
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.
Good suggestion
$c.$f($d $($extra1)*) $($extra2)* | ||
}; | ||
} | ||
match_cal_general!($cal, pattern_cal_date, helper, [d, blah], { |
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.
The identifier blah
is used here for the Hijri algorithm parameter. This is a poor choice for a variable name as it's typically used for placeholder code and can be confusing for future readers. In other parts of this file, like in the add
and until
methods, alg
or alg1
/alg2
are used for this purpose. For consistency and clarity, please rename blah
to alg
.
match_cal_general!($cal, pattern_cal_date, helper, [d, alg], {
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.
Good suggestion
Gemini wants to be my friend, clearly. I wonder if it would say the same thing for the opposite PR. We can test it later. |
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.
I've spent a good 10 minutes trying to understand how this works and have given up. This change is an immense readability hurdle; this code should be kept simple enough for any contributor to update.
I also don't see the type of bug that you're saying this avoids. Please update the PR description with why this PR is justified, not just where this code was previously discussed.
I plan to look at this, but not until this afternoon unfortunately. |
So I've been thinking about this. We basically have a macro complexity-utility tradeoff. Most of our macros, including the current-on- Beyond such simple macros, I think "complexity" comes in a couple flavors (labels are not intended to be precise):
Utility also comes in a couple flavors:
Okay, so let's look at, say, the
And in the utility column, it ticks all of the boxes too: the short macro call makes it easy to see what's important (Readability), and probably prevents mistakes (Mistake prevention). What about a more complex macro,
And utility wise, it's reducing a lot of boilerplate, and is amazing at bringing the important bits to the forefront both for readability and mistake prevention. Alright, let's apply that here. Note that I'm not trying to come up with a rubric that "gives us an answer", I'm just trying to come up with a rubric that brings to the forefront the key points about a particular macro to aid this discussion.
And from the utility column, it's again reducing a lot of boilerplate. However I'm not sure if there's much of a readability delta: the code it's replacing is, to me, not super hard to understand at a glance. The thing I mentioned before of using I think it has a slight benefit in Mistake prevention since it would prevent getting the match arms wrong, but for only two additional callsites compared to the status quo I'm not too worried, and Rust forces us to add the right match arms anyway because of crate-internal exhaustiveness. There's also a slight benefit with making sure everyone handles the HijriTabular sighting type. With this framing, personally I think I see this macro as having a fair amount of additional complexity without much additional utility. I weakly believe that the utility does not justify the added complexity. I appreciate being able to review this as a separate change, it's easier to see the now-more-advanced add/until code with and without the macro. |
My expectation was 3-5 minutes to learn how to use the macro. The macro is intended to be general enough that you won't normally need to modify it. If you do need to modify it, 10-15 minutes would be my expectation. For example, I wouldn't dare to understand how
Added.
Yes, I agree with @Manishearth's characterizations. |
This reverts commit ed48da7.
Also, we should normalize passing macros as arguments to other macros. It's a powerful technique that, while not the first thing you learn in Rust, greatly improves macro brevity and readability for those who are similarly initiated. We shouldn't discourage its use due to unfamiliarity. (no promises, but I have that topic flagged as material for a blog post.) |
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 is a test
I think it's often hard to keep track of the "signature" of macros in your head, and higher-order macros make this worse since you now need to keep track of that signature while keeping track of other stuff. It's still a powerful and useful trick, though. |
No we should not. It requires uncoditional trust in the macro, because it's impossible to follow what's happening.
Many macros don't even have a single signature, they have dozens. |
That is not a reasonable time to expect someone to onboard onto this otherwise trivial code.
ok
both of these things we have successfully managed for 5 years. we have code reviews, tests, and we're not stupid |
This was pulled out of #7012. I considered that PR to be pushing AnyCalendar complexity over the edge to justify a new macro, but @robertbastian asked for it to be considered separately. I disagree with a process that forces authors to split stylistic changes out of otherwise-mergeable PRs, especially given time zone differences, but let's discuss the process in another thread.
Examples of bugs this can prevent: