-
Notifications
You must be signed in to change notification settings - Fork 61
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
Conversation
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. |
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 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?
Absolutely no worries! Take as much time as you need. |
Hmm, where do I do that?
Heh, I honestly forget all the different kinds of tests; that's what CI is for 😉 The main things I do:
|
Line 1251 in 42bcc33
Thanks! |
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.
First pass through. It seems to be looking good; a few questions / comments.
pub struct Dummy0; | ||
|
||
#[derive(Debug)] | ||
pub struct Dummy1; |
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.
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.
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.
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.
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.
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.
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.
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.
6e0411e
to
c099bff
Compare
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.
super::
is problematic; would checking forsuper
, and just prepending anothersuper
be Good Enough™ ? I think a similar prependsuper
approach would work for visibility too (possibly addingin
).
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.
fn can_set_module_visibility_restricted() { | ||
let _ = self::child::restricted_error::VariantSnafu { v: Dummy3 }.build(); | ||
} |
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 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; |
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.
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.
FWIW, I pushed some extra commits to your branch fixing some minor nits instead of making you fix them ;-) |
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
Yes, exactly.
The module could be 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,
};
}
That's exactly what I was thinking, and would be happy to wait on implementing it until someone wants it.
Keeping the suffix would mean wrapping an error would look like: let output = some_risky_operation()
.context(error::RiskySnafu {
// ...
})?; With
You think it'll be common for people to switch existing code to the module mode? |
Hey! Just wanted to check to see if we're waiting on me for anything. If not, no rush! |
Nothing in particular, just life happening.
So, what do you feel we should do? We could make the statement that it's a known limitation for now, We could even scrape public code that uses SNAFU later on to see how they use it.
I'm not sure, but I think "yes" in a few cases/ways:
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 |
I'm okay just leaving it broken for now, and maybe looking into prepending
Yeah, that makes sense.
Sounds good! 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. |
15cc66b
to
97b87b4
Compare
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. |
I'll try and take a look before next week, but I've been a bit swamped. Sorry 😅 |
you and me both; apologies appreciated but not needed! |
97b87b4
to
54d1881
Compare
✔️ 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 |
54d1881
to
222447b
Compare
222447b
to
24d12bf
Compare
OK, made a small push and behavior change. Now the defaults are to generate a private module and use |
The new attribute `module` makes the derive macro put all the context selectors for structs and enums in a module. Closes shepmaster#188
18255ad
to
0978fa1
Compare
Thanks! Published in 0.7.0-beta.2. Getting really close! |
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 forsuper
, and just prepending anothersuper
be Good Enough™ ? I think a similar prependsuper
approach would work for visibility too (possibly addingin
).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?