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

Store lifecycle hooks dynamically #162

Merged
merged 2 commits into from
Nov 29, 2023
Merged

Store lifecycle hooks dynamically #162

merged 2 commits into from
Nov 29, 2023

Conversation

9999years
Copy link
Member

Instead of having a field for each event (before_restart_ghci, test_ghci, after_startup_shell, etc.), we generate and store the arguments dynamically. This simplifies the running of hooks and will hopefully make it easier to extend the lifecycle hooks in the future.

@github-actions github-actions bot added the patch Bug fixes or non-functional changes label Nov 22, 2023
src/hooks.rs Outdated
Comment on lines 65 to 72
fn supported_when(&self) -> Vec<When> {
match self {
LifecycleEvent::Test => vec![When::During],
LifecycleEvent::Startup | LifecycleEvent::Reload | LifecycleEvent::Restart => {
vec![When::Before, When::After]
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think rather than check at runtime that we don't have an invalid combination of LifecycleEvent and When we should enforce that statically by combining them into a single data structure using something like this:

pub enum Bounded {
    Before,
    After,
}

pub enum LifecycleEvent {
    Test,
    StartUp{ when: When },
    Reload{ when: When },
    Restart{ when: When },
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I did consider this, but it gets kind of clumsy; for one, I'd have to add an explicit list of each possible event:

fn events() -> Vec<LifecycleEvent> {
  vec![
    LifecycleEvent::Test,
    LifecycleEvent::StartUp(When::Before),
    LifecycleEvent::StartUp(When::After),
    // ...
  ]
}

With that approach, when I add a new lifecycle event, nothing will tell me to add the new event to the list, and I also have to make sure to include every possible variant.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't that be an issue even for the existing approach?

Copy link
Member

@evanrelf evanrelf Nov 29, 2023

Choose a reason for hiding this comment

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

supported_when (the function you highlighted) matches on a value of type LifecycleEvent. If you added another event, the pattern match would become non-exhaustive.

Copy link
Member Author

Choose a reason for hiding this comment

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

And I have a proc macro that generates a function which iterates over all the enum variants, which I can only use if they don't contain data.

Copy link
Contributor

Choose a reason for hiding this comment

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

it is true that in that one case you might lose exhaustiveness, but on the balance I feel like you gain better safety/exhaustiveness by making invalid states unrepresentable

Copy link
Member Author

Choose a reason for hiding this comment

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

Consider that the impetus of this system is that I actually already forgot to fill in new entries in the old lifecycle hooks list! This system prevents that. If there's a way to implement this data type that makes invalid states unrepresentable and doesn't require me to explicitly list all the valid states, I'll gladly implement it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify which list you're referring to? I understand what you're asking for in the abstract but couldn't place where in each pull request this corresponds to

evanrelf
evanrelf previously approved these changes Nov 29, 2023
src/hooks.rs Outdated Show resolved Hide resolved
src/hooks.rs Outdated
}

impl LifecycleEvent {
fn supported_when(&self) -> Vec<When> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fn supported_when(&self) -> Vec<When> {
fn supported_when(&self) -> HashSet<When> {

src/hooks.rs Outdated
}
}

fn supported_kind(&self, when: When) -> Vec<CommandKind> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fn supported_kind(&self, when: When) -> Vec<CommandKind> {
fn supported_kind(&self, when: When) -> HashSet<CommandKind> {

Comment on lines +191 to +192
impl<C> Hook<C> {
fn with_command<C2>(&self, command: C2) -> Hook<C2> {
Copy link
Member

Choose a reason for hiding this comment

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

Is the C2 necessary? C isn't constrained in any way, so I think it would always unify with whatever the type of command is.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, because this lets you change the generic type (i.e. it lets you go from a Hook<CommandKind> to a Hook<Command>). That's also why I can't use the ..self record update syntax here — the types need to match exactly.

@evanrelf
Copy link
Member

evanrelf commented Nov 29, 2023

This is 100% overkill but

You could replace HashSet<T> (where T is When or CommandKind) with FlagSet<T> from the flagset crate (nicer alternative to bitflags if you're familiar with that).

In this case it's so unnecessary, but the flagset crate is actually really nice, so I recommend considering it for future stuff.

This converts the lifecycle hook arguments (like
`--before-startup-shell`) to be generated and stored dyanmically.
This makes running them and querying them easier.
@9999years 9999years merged commit 253d009 into main Nov 29, 2023
28 checks passed
@9999years 9999years deleted the rebeccat/dynamic-hooks branch November 29, 2023 23:36
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
patch Bug fixes or non-functional changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants