-
Notifications
You must be signed in to change notification settings - Fork 178
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
Add simpler macro versions of ULE/VarULE macros to reduce syn
deps
#5127
Comments
There are a bunch of similar but distinct use cases for simple transparent wrappers.
Furthermore:
|
How feasible is it to write |
I proposed one solution in #5101. What other solutions might be considered? |
@sffc I'm confused, this issue is proposing the other solution. It's listing out the macros that could potentially be written. I know how to write these macros. I'm not writing them yet because i want to figure out what the full gamut is before I start designing them. Is there something else you're looking for here?
It's ... really annoying I'd say. You'd end up implementing a half-baked parser.. Now, you could do something like implementing a macro that gets called as Honestly, for the thing this issue is about, MBEs are quite sufficient. For things not listed in my comment above, I don't think a non-syn impl will be easier than MBE since MBEs actually let you implement a half-decent parser without writing an actual parser. |
I'm also not prioritizing working on this: I don't perceive this issue as urgent compared to 2.0. Please let me know if you think this should be higher priority, happy to start looking at this properly, but so far I don't see any reason to rush this. It's good to document all of the fruit hanging at any height for #5124 so that we have an idea of potential wins, but I'm not yet convinced I should spend time on this vs 2.0 issues. (and 2.0 diplomat still needs work) |
But I wrote these macros in the PR, and the way I wrote them was rejected. Currently the issue is describing the use cases behind the macros I proposed without proposing a concrete alternative.
You've said this, but I don't know what's in your head, and what tradeoffs different ways of writing the macros could entail.
This issue is unlikely to ever be higher priority than milestone tasks, but we've (or at least I've) been wanting these macros for a long time and we keep incurring debt without them. I opened my PR when I did because I felt I could justify the work since I was expecting two more of these landing this week (DataKeyAttributes and PatternULE which is needed for units patterns). We also delayed the Pattern rewrite for a long time since it was never higher priority than milestone tasks. |
I don't want to actually put in the effort of writing these macros until the full set of use cases is enumerated. Making macros flexible is a lot of work, and it is ideal to come in understanding the full desired extent of flexibility. And no, this is not describing the use cases behind the macros you proposed, it describes multiple others as well: you covered one of these use cases with that PR.
I don't see the point of going deep down implementation land until we know what we're hoping to cover. For that I need to look at the actual potential callsites. What's in my head is more or less an understanding of what is possible with the macro system, it seems like you think I have a very specific implementation in mind? I have multiple ideas, and could implement it one way or the other, but I don't think we're there yet. The hope is to not have any external difference: no new traits, no new per-crate unsafe code, just replacing a safe derive or make_ule with a safe wrapping macro invocation. I believe I already put forth a rough sketch earlier in that thread, but here is what one such macro invocation can look like: impl_wrapper_varule!(
[cast: (cast_ref, cast_as_ref, cast_box, ..), ... other options .... ],
/// Docs
#[derive(..)]
pub struct FooWrapper(str);
); and basically the macro applies the transparent for you. Implementing the "options" will get tricky but not too tricky as long as you pick a good syntax. Handling pub/priv stuff will be annoying. Highlighting that this is an example, the exact syntax and mechanisms are not yet something I've decided upon. Happy to talk more about the design but it's still not clear to me what you want to know about it. I know enough to know that this is possible, but I can't have a more concrete design until I better understand all the use cases, which is precisely what I'm trying to do with this issue. |
Sure, but for me actually work on this before 2.0, some other 2.0 blocker work item is getting pushed back. Diplomat stuff is taking more time than expected so I'm trying to be careful with what I take on. I'd rather schedule this as 2.x priority: to be fixed basically immediately after 2.0. |
Unless there's a reason this will impact the 2.0 design in some way: which is not entirely out of the question! If we can break a bunch of dependency links, and maybe add some features, that's a valid reason to make a breaking change that enables that. Which is another reason I want to understand the use cases first, so we can see which crates we can liberate here. But also so far I don't see a reason for these macros to cause 2.0 breakages. If there are, do let me know. (One potential one is if we discover that e.g. properties can be 100% no-proc-macro if we turn bidi/ccc/whatever into a feature flag. Though that particular one can still be done stably. There may be others like it) |
In terms of what's needed: honestly just The other 40% would be to also have support for a But even just the |
@sffc I'd like some more detail on ULE vs VarULE. The rough three-way split in #5127 (comment) came from browsing through impls and realizing it's not as simple as one macro that "just does ref-cast impls". |
I wasn't distinguishing between ULE and VarULE. I don't immediately see why that matters for the design of the solution: The actual A fairly clean solution would be to say that it is expected for these types to have a function named I think we can largely depend on the signature of the function for enforcing ULE/VarULE invariants, maybe with a little extra work in the codegen. For example, #[repr(transparent)]
pub struct LowercaseStr(str);
impl_stuff!(LowercaseStr, str);
impl LowercaseStr {
pub fn try_from_raw(raw: &str) -> Result<&Self, ParseError> {
if str.is_ascii_lowercase() {
Ok(Self::from_raw_unchecked(raw))
} else {
Err(ParseError::NotLowercase)
}
}
} And impl LowercaseStr {
const fn from_raw_unchecked(raw: &str) -> &Self {
unsafe { core::mem::transmute(raw) } // or a pointer cast, etc.
}
}
unsafe impl VarULE for LowercaseStr {
fn validate_byte_slice(bytes: &[u8]) -> Result<(), UleError> {
// Thought: we need to pass a fn to the macro that first validates [u8] -> str
let raw = core::str::from_utf8(bytes)?;
Self::try_from_raw(raw).map(|_| ())
}
unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self {
let raw = core::str::from_utf8_unchecked(bytes);
Self::from_raw_unchecked(bytes)
}
} What is wrong with this? |
You're listing two use cases: our codebase has more, in particular, the ULE use case you list is not what we do in icu_properties, the crate that spawned this discussion. See point 3 in my use case list in #5127 (comment) I started looking at this issue with the assumption that all we needed was ref-casting and realized that properties was doing something different. Perhaps properties doesn't need to be doing that, I'm not sure if it does, but it is currently doing something that cannot be replicated with a simple ref-cast macro. For wrapper ULE there's broadly four different kinds of struct impls I see in the wild, for situations without further validation code:
Not all of these cases may be strictly necessary, in particular it feels like 3 is not worth it when you can just do 2, reflexive ULE types are better. But 3 is what And that's just "ULE wrapper types". We also have "ULE structs", "ULE enums", and "VarULE wrapper types". These each probably have a single use case, and we can defer structs/enums till later, but that's still a fair number of things. |
Some general principles for a wrapper type
If we consistently applied these principles, would it make things cleaner/easier? |
One thing that might be making properties special is that there are some properties that are #[repr(transparent)]
#[zerovec::make_ule(ScriptULE)]
pub struct Script(pub u16);
#[repr(transparent)]
#[zerovec::make_ule(HangulSyllableTypeULE)]
pub struct HangulSyllableType(pub u8); I guess the simplest way to clean that up would be to make all of the structs use their inner integer's ULE for their AsULE. |
Yes, I think! However, there's a valid discussion to be had whether in the bottom left case |
Also worth noting that this doesn't even need ref_cast impls since there in your table there are no new |
Oh, also, note: #[repr(transparent)]
pub struct LowercaseStr(str);
impl_stuff!(LowercaseStr, str); This would have to be impl_stuff!(pub struct LowercaseStr(str)); unfortunately since we cannot detect the presence of the |
In favor. Perhaps we should do this refactoring first, see what we end up with, and move on from there? I think we have other inconsistencies in our ULE impls. And this refactoring is a breaking change so needs to happen pre 2.0. |
If the bottom left case was
SGTM |
Ah, yeah. It's nicer for types that already contain reflexive ULEs. So I guess it's a choice between special casing those (and having nice reflexive ULE apis) or having cleaner and more consistent macros. I'm fine with either, I don't think those nice reflexive ULE APIs are that useful. |
I realized that although we can/should reduce the number of impls of |
See #2310
In #5124 we're trying to slim down ICU4X's deps. A lot of our crates don't need the full power of the ule/varule macros, and it would be nice to expose MBE macro versions of them.
The text was updated successfully, but these errors were encountered: