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

Implement #[snafu(module)] support #295

Merged
merged 1 commit into from Nov 15, 2021

Conversation

SamWilsn
Copy link
Contributor

@SamWilsn SamWilsn commented Jul 6, 2021

Here's a rough first pass at implementing submodule support. This is my first time in proc-macro land, so apologies if anything is implemented horribly!

There are a couple points that I wanted to discuss before doing too much more work:

  • Like @shepmaster mentioned in Change the default context selector suffix to Snafu #286, super:: is problematic; would checking for super, and just prepending another super be Good Enough™ ? I think a similar prepend super approach would work for visibility too (possibly adding in).

  • The visibility of types inside the module has to be at least as restrictive as the module itself, which seems to work the way I have it now, but I'd like another pair of eyes to take a look.

  • I've somewhat overloaded the visibility attribute to control both the module and the types in the module (to "solve" the previous point.) Would there be much value in controlling the visibility of types in the module separately from the module itself, and if so, how would you like it to look?

  • Would you be horribly opposed to clearing the suffix (by default) if module is enabled?

@shepmaster
Copy link
Owner

Thanks! I didn't expect a PR quite so soon 😊.

As a word of caution, I'll probably be a bit slow in reviewing this due to other responsibilities, but I'm excited to read through it.

As a meta question, since you are a new contributor to this code base, how did you find the process of contributing? I'm familiar enough with the code so it's harder for me to make meaningful judgements about its quality and extensibility.

@SamWilsn
Copy link
Contributor Author

SamWilsn commented Jul 6, 2021

As a meta question, since you are a new contributor to this code base, how did you find the process of contributing? I'm familiar enough with the code so it's harder for me to make meaningful judgements about its quality and extensibility.

Generally I think it's pretty easy to work with. I've seen much worse Rust code bases. Some bits of struck me as slightly weird, like shadowing ContextSelector, but again, I think it's generally good!

One question I had, and it might be documented somewhere, is how do I run the full test suite locally, if I want to make sure I didn't break backwards compatibility?

As a word of caution, I'll probably be a bit slow in reviewing this due to other responsibilities, but I'm excited to read through it.

Absolutely no worries! Take as much time as you need.

@shepmaster
Copy link
Owner

like shadowing ContextSelector

Hmm, where do I do that?

how do I run the full test suite locally

Heh, I honestly forget all the different kinds of tests; that's what CI is for 😉 The main things I do:

  1. cargo test — catches the majority of cases
  2. for i in compatibility-tests/*; do pushd $i; cargo test; popd; done — you do have to watch a bit to look for the failures though.
  3. Then there are the weird ones like cargo +nightly -Z minimal-versions update which requires all sorts of Very Extra Special Setup.

@SamWilsn
Copy link
Contributor Author

SamWilsn commented Jul 7, 2021

like shadowing ContextSelector

Hmm, where do I do that?

use crate::shared::ContextSelector;

...

Thanks!

Copy link
Owner

@shepmaster shepmaster left a comment

Choose a reason for hiding this comment

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

First pass through. It seems to be looking good; a few questions / comments.

snafu-derive/Cargo.toml Show resolved Hide resolved
snafu-derive/src/lib.rs Outdated Show resolved Hide resolved
snafu-derive/src/lib.rs Outdated Show resolved Hide resolved
snafu-derive/src/lib.rs Outdated Show resolved Hide resolved
snafu-derive/src/parse.rs Outdated Show resolved Hide resolved
pub struct Dummy0;

#[derive(Debug)]
pub struct Dummy1;
Copy link
Owner

Choose a reason for hiding this comment

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

What value do the multiple dummy structs all at the same level bring? I'd have expected to see them scattered across different modules (and then imported in interesting ways in the error enum) to exercise how the importing works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the variants all have the same name, I wanted to make sure the generated selectors had the correct type in them. Probably overkill though, so I can remove them all if you like.

Copy link
Owner

Choose a reason for hiding this comment

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

Since the variants all have the same name

This is actually the biggest thing to me that I hadn't even quite realized before today! I've not run into that specific problem, but I've heard of some other people who had.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh god, it's half of my motivation for this PR. I'll have a constructor that can fail in A, B, C ways, then the rest of the methods can fail in A, D, E, F ways, and the A context struct will conflict.

tests/module.rs Show resolved Hide resolved
tests/module.rs Show resolved Hide resolved
Copy link
Owner

@shepmaster shepmaster left a comment

Choose a reason for hiding this comment

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

super:: is problematic; would checking for super, and just prepending another super be Good Enough™ ? I think a similar prepend super approach would work for visibility too (possibly adding in).

I confess to being a bit surprised that there aren't any test failures here. Perhaps the use super::* is enough for our purposes? For example, this is consistent and works:

mod alpha {
    pub struct A;
}

mod gen {
    use super::*;

    fn x(_: alpha::A) {}
}

Do you have any test cases up your sleeve where the current implementation might fail?

The visibility of types inside the module has to be at least as restrictive as the module itself, which seems to work the way I have it now, but I'd like another pair of eyes to take a look.

Are you referring to the context selectors as the "types inside the module"? Why do you say they have to be at least as restrictive (but could be more restrictive?). If they were more restrictive, wouldn't we not be able to use them?

I've somewhat overloaded the visibility attribute to control both the module and the types in the module (to "solve" the previous point.) Would there be much value in controlling the visibility of types in the module separately from the module itself, and if so, how would you like it to look?

I expect that there is some value in it, but maybe it doesn't need to be immediately implemented.

Some ideal would be if we could compute the "minimum" of all of the visibilities of the selectors, but that seems like an overly clever and brittle idea...

One strawman syntax to propose would be #[snafu(module(name, visibility(...)))] or #[snafu(module(name(...), visibility(...)))].

Would you be horribly opposed to clearing the suffix (by default) if module is enabled?

I'm torn! Doing a bit of thinking and asking other folk, I think I'd prefer to maintain consistency and keep the suffix. This means that people would be able to easily switch to using a module with a use the_name::*.

The "good" news is that once someone is using the #[snafu] attribute, adding a bit more to disable the suffix shouldn't be too painful. 🤞

I'm not against changing it in a future version, though, based on feedback.

Comment on lines +46 to +48
fn can_set_module_visibility_restricted() {
let _ = self::child::restricted_error::VariantSnafu { v: Dummy3 }.build();
}
Copy link
Owner

Choose a reason for hiding this comment

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

The visibility tests would benefit from negative test case(s) in the compatibility-tests/compile-fail/ directory. Let me know if you need help with that.

pub struct Dummy0;

#[derive(Debug)]
pub struct Dummy1;
Copy link
Owner

Choose a reason for hiding this comment

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

Since the variants all have the same name

This is actually the biggest thing to me that I hadn't even quite realized before today! I've not run into that specific problem, but I've heard of some other people who had.

@shepmaster
Copy link
Owner

FWIW, I pushed some extra commits to your branch fixing some minor nits instead of making you fix them ;-)

@SamWilsn
Copy link
Contributor Author

I confess to being a bit surprised that there aren't any test failures here. Perhaps the use super::* is enough for our purposes? [...] Do you have any test cases up your sleeve where the current implementation might fail?

I do!

struct BadError {
    // ...
}

mod inner {
    #[snafu(module)]
    enum Error {
        Bad { source: super::BadError }
    }
}

Which expands to, roughly:

struct Bloop {
    // ...
}

mod inner {
    mod error {
        use super::*;

        enum Error {
            Bad { bloop: super::Bloop }
        }

        struct BadSnafu {
            bloop: Into<super::Bloop>,
        }
    }
}

The two super::Bloop types won't correctly resolve. They'd have to be super::super::Bloop.

The visibility of types inside the module has to be at least as restrictive as the module itself, which seems to work the way I have it now, but I'd like another pair of eyes to take a look.

Are you referring to the context selectors as the "types inside the module"?

Yes, exactly.

Why do you say they have to be at least as restrictive (but could be more restrictive?). If they were more restrictive, wouldn't we not be able to use them?

The module could be pub(crate) and the context selectors could be pub(super) and still be useful, I think. In any case, that's what I meant by "more restrictive".

This example illustrates the problem when the visibilities aren't correct, I think, though I might have messed it up a little from memory:

struct Bar;

pub(crate) mod a_module {
    pub struct Foo {
        pub x: crate::Bar,
    }
}

fn main() {
    let x = a_module::Foo {
        x: Bar,
    };
}

playground

One strawman syntax to propose would be #[snafu(module(name, visibility(...)))] or #[snafu(module(name(...), visibility(...)))].

That's exactly what I was thinking, and would be happy to wait on implementing it until someone wants it.

Would you be horribly opposed to clearing the suffix (by default) if module is enabled?

I'm torn! Doing a bit of thinking and asking other folk, I think I'd prefer to maintain consistency and keep the suffix.

Keeping the suffix would mean wrapping an error would look like:

let output = some_risky_operation()
    .context(error::RiskySnafu {
        // ...
    })?;

With error:: there, I definitely feel like Snafu is redundant, but I don't want to be too pushy about it.

This means that people would be able to easily switch to using a module with a use the_name::*.

You think it'll be common for people to switch existing code to the module mode?

@SamWilsn
Copy link
Contributor Author

SamWilsn commented Aug 9, 2021

Hey! Just wanted to check to see if we're waiting on me for anything. If not, no rush!

@shepmaster
Copy link
Owner

shepmaster commented Aug 21, 2021

we're waiting on me for anything

Nothing in particular, just life happening.

The two super::Bloop types won't correctly resolve.

So, what do you feel we should do? We could make the statement that it's a known limitation for now, :shipit: and potentially improve it in the future (yay for tests helping us not slide backwards). Maybe create an issue for people to weigh in on as they use it to provide feedback.

We could even scrape public code that uses SNAFU later on to see how they use it.

common for people to switch existing code to the module mode

I'm not sure, but I think "yes" in a few cases/ways:

  1. I have an enum with { A, B } and then create a new enum with { B, C }. I'd have to move at least one of them, but would probably move both for consistency.
  2. "Switching" as in mentally changing gears. If the macro behaves differently for modules and non-modules beyond just modules, it might be confusing. I'm a bit gunshy because this has been the hands-down most confusing aspect of SNAFU so far.

With error:: there, I definitely feel like Snafu is redundant,

I think you are right, which is why I would want to continue to allow people to remove it. There are (admittedly obscure) cases where the suffix might be useful still, such as
when someone glob-imports the enum variants and the context selectors (ignoring both natural namespacing tools).

@SamWilsn
Copy link
Contributor Author

The two super::Bloop types won't correctly resolve.

So, what do you feel we should do? We could make the statement that it's a known limitation for now, :shipit: and potentially improve it in the future (yay for tests helping us not slide backwards). Maybe create an issue for people to weigh in on as they use it to provide feedback.

We could even scrape public code that uses SNAFU later on to see how they use it.

I'm okay just leaving it broken for now, and maybe looking into prepending super:: as a longer term solution? There might even be a more, er, standardized way of doing it. I doubt snafu would be the only proc-macro with this issue.

common for people to switch existing code to the module mode

I'm not sure, but I think "yes"

Yeah, that makes sense.

With error:: there, I definitely feel like Snafu is redundant,

I think you are right, which is why I would want to continue to allow people to remove it.

Sounds good!

So what's next?

@shepmaster
Copy link
Owner

So what's next?

Do you have any specific code changes you want to make before merging? Otherwise, we can transition out of WIP state.

Can you list out all the known issues/missing features/future work?

We’ll need to document this in the guide somehow. I tend to have a heavy hand in editing that to maintain consistent tone, but if you’d like to produce the first few drafts, that’d be a huge help. That doesn’t have to be this PR though.

@shepmaster shepmaster changed the title WiP: Implement #[snafu(module)] support (#188) Implement #[snafu(module)] support (#188) Sep 15, 2021
@shepmaster shepmaster changed the title Implement #[snafu(module)] support (#188) Implement #[snafu(module)] support Sep 15, 2021
@shepmaster shepmaster linked an issue Sep 15, 2021 that may be closed by this pull request
@shepmaster
Copy link
Owner

I hope to spend some time on this in the next week or so. If you have a chance to address the last comment, that'd be great. Otherwise, I'll see what I can do.

@SamWilsn
Copy link
Contributor Author

I'll try and take a look before next week, but I've been a bit swamped. Sorry 😅

@shepmaster
Copy link
Owner

I've been a bit swamped.

you and me both; apologies appreciated but not needed!

@netlify
Copy link

netlify bot commented Sep 29, 2021

✔️ Deploy Preview for shepmaster-snafu ready!

🔨 Explore the source changes: 0978fa1

🔍 Inspect the deploy log: https://app.netlify.com/sites/shepmaster-snafu/deploys/619019b31b080f0008e4c406

😎 Browse the preview: https://deploy-preview-295--shepmaster-snafu.netlify.app

@shepmaster
Copy link
Owner

OK, made a small push and behavior change. Now the defaults are to generate a private module and use pub(super) visibility for the selectors inside the module. This prevents some warnings ("private type FooSnafu in public interface") and avoids the module becoming publicly visible and documented (!).

@shepmaster shepmaster marked this pull request as ready for review November 13, 2021 19:16
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
@shepmaster shepmaster merged commit e846c56 into shepmaster:main Nov 15, 2021
@shepmaster
Copy link
Owner

Thanks! Published in 0.7.0-beta.2. Getting really close!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option to put generated context selectors into a submodule
2 participants