-
-
Notifications
You must be signed in to change notification settings - Fork 18
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 enum discriminants #337
Add enum discriminants #337
Conversation
Also, on why
|
6cde9fe
to
4bd03a1
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.
I don't think we can get away from parsing the enum's repr
, since the existence of an explicit repr
sometimes causes discriminants to be defined implicitly. I suspect those implicit discriminants might not be part of rustdoc JSON, but I've suggested a test that can check that.
There's already some attribute-parsing code in the adapter, so you don't have to reinvent the wheel there.
To more generally address some of the previous comments due to me leaving stuff out of docs:
|
I just remembered that the datatype of I'll hold off on that until I hear back from you. |
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 like where this is going! Thanks for putting it together!
I think the major design question is what to do with implicitly-defined discriminants.
Since lints care about whether the discriminant's value is well-defined, and don't care about how it's defined, I think it'll be more helpful for lints if we always provide the discriminant if it's well-known.
I made a case for this within.
expr: String! | ||
|
||
""" | ||
The numerical value of the discriminant. Stored as a string. Ranges from `i128::MIN` to `u128::MAX`. |
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.
Minor phrasing tweak suggestion:
The numerical value of the discriminant. Stored as a string. Ranges from `i128::MIN` to `u128::MAX`. | |
The numerical value of the discriminant, represented as a string. | |
May range from `i128::MIN` to `u128::MAX`. |
I think it's probably fine to keep it as a string. If you don't want to include the data type in the schema, you don't have to. I don't think it'll be strictly necessary to compute discriminant values, since they'll get turned into strings anyway. It might be worth trying to add a fast path that avoids 128-bit integer math, and uses 64-bit math as much as possible when computing implicit discriminants. 128-bit ints can be quite expensive, and almost no enums will actually use them in practice. |
Co-authored-by: Predrag Gruevski <[email protected]>
I think
Even if I had the type, parsing explicit discriminants is hard because I don't know the type until I a number becomes too large or too small for the type I presumed. Below is what I believe is the minimum code for representing any possible discriminant value. I believe use of enum DiscriminantValue {
I64(i64),
U64(u64),
I128(i128),
U128(u128),
}
impl DiscriminantValue {
fn increment(self) -> DiscriminantValue {
match self {
DiscriminantValue::I64(i) => {
match i.checked_add_unsigned(1) {
Some(i) => i.into(),
None => {
match u64::try_from(i) {
Ok(u) => DiscriminantValue::from(u).increment(),
// i is a negative i128
Err(_) => DiscriminantValue::from(i128::try_from(i).unwrap()).increment(),
}
},
}
},
DiscriminantValue::U64(i) => {
match i.checked_add(1) {
Some(i) => i.into(),
None => {
match i128::try_from(i) {
Ok(u) => DiscriminantValue::from(u).increment(),
// i is a u128
Err(_) => DiscriminantValue::from(u128::try_from(i).unwrap()).increment(),
}
},
}
},
DiscriminantValue::I128(i) => {
match i.checked_add_unsigned(1) {
Some(i) => i.into(),
// i is a u128
None => DiscriminantValue::from(u128::try_from(i).unwrap()).increment(),
}
},
DiscriminantValue::U128(i) => (i + 1).into(),
}
}
}
impl From<i64> for DiscriminantValue {
fn from(value: i64) -> Self {
DiscriminantValue::I64(value)
}
}
impl From<i128> for DiscriminantValue {
fn from(value: i128) -> Self {
DiscriminantValue::I128(value)
}
}
impl From<u64> for DiscriminantValue {
fn from(value: u64) -> Self {
DiscriminantValue::U64(value)
}
}
impl From<u128> for DiscriminantValue {
fn from(value: u128) -> Self {
DiscriminantValue::U128(value)
}
}
impl FromStr for DiscriminantValue {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(i) = i64::from_str(s) {
return Ok(i.into());
}
if let Ok(i) = u64::from_str(s) {
return Ok(i.into());
}
if let Ok(i) = i128::from_str(s) {
return Ok(i.into());
}
match u128::from_str(s) {
Ok(i) => Ok(i.into()),
Err(e) => Err(e),
}
}
} |
I have a few concerns that could be resolved by seeing more of how you plan to integrate that enum into the broader design:
|
Spent some time while on the move simplifying Test looks like it demonstrates expected behavior; perhaps should include more pathological results like i128 and u64 instances. If you think of a more functional and less mutable way of calculating the discriminants before I do, let me know. I'll more neatly integrate the algo into the codebase in terms of lifetimes once I'm at my desk again. I think I'm going to calculate the discriminants all at once when the Finally, I have decided that I'm going to remove |
This looks quite reasonable, thank you! I'd recommend a couple of small-ish tweaks, in addition to recommending more test coverage with i128 and u128 discriminants before we merge. Consider either Also, consider (either in this PR or in a subsequent one) making it so that the discriminants are only computed when a discriminant edge is requested, not a variant edge? This is because there will be lots of lints that touch variants without touching discriminants, and we'd prefer to be as lazy as possible here. Ideally, we'd also memoize the computed discriminant strings if possible so we don't compute them multiple times if we have multiple lints over them. The outcome we'd like to avoid in the long run is having many lints repeatedly compute discriminant strings (and do a bunch of allocations in the process) even though very few of them actually ever need discriminants. |
I was thinking about how to do the former the other day; the Rustdoc just makes it really annoying to get a
Does Trustfall have some kind of general caching utility? If not, caching for our use case could be easily achieved with |
Here's what I'd do. Right now, variants are represented as #[non_exhaustive]
#[derive(Debug, Clone)]
pub struct EnumVariant<'a> {
item: &'a Item,
discriminant: todo!("< something clever that holds the parent `&'a Enum` internally and is lazily initialized >")
} Then on first access to the This sidesteps the problem of getting the |
Realized that in order to do the first thing, I'll be 90% of the way to doing the other, so might as well do it all in one patchset. |
After much fighting with Will add |
Messed up CI; on that. |
82dfa3e
to
3f99cd7
Compare
3f99cd7
to
8f50c70
Compare
Aside: getting pre-commit hooks going for |
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 looking pretty good!
I made some suggestions on how to avoid some cloning and eager evaluations to further improve performance. Let me know if you'd like to pair-program any of this — sometimes working with iterators and lifetimes gets complicated, and I've debugged a lot of those errors so I'm happy to help.
Re: pre-commit hooks, I actually really hate tools that prevent me from committing since I do a lot of quick-and-dirty experimentation. But if you wanted to set it up for yourself in a way that doesn't impose a mandatory workflow change on other contributors, I'm fine with that.
We'll come back to ordered variants another time. |
Otherwise, the patch should be done now! |
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.
Nice! Just some test improvements and polish items left. There appears to be a merge conflict too, which should be an easy fix.
I've retargeted the PR to branch rustdoc-v33
instead of rustdoc-v28
, so that we merge into the current development branch. That way I can easily port this to older versions, since I have some automation to handle that.
Co-authored-by: Predrag Gruevski <[email protected]>
I believe this should be it. |
Also, CI's been acting a bit weird these past few days; would you know why? |
Still seeing merge conflicts on my end, can you try pulling the latest upstream GitHub doesn't trigger CI runs on PRs that are unmergeable due to conflicts. |
b77a518
to
c164df6
Compare
D'oh. Think I did it. |
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.
Excellent, thank you!
Any interest in continuing to work on variant order / struct field order? Or too busy with schoolwork? No pressure — cargo-semver-checks will be here if/when you want to come back to contributing — I just wanted to know whether you wanted dibs on those extensions or if I should put them up for grabs.
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation Co-authored-by: Predrag Gruevski <[email protected]> * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant Co-authored-by: Predrag Gruevski <[email protected]> * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Predrag Gruevski <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation Co-authored-by: Predrag Gruevski <[email protected]> * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant Co-authored-by: Predrag Gruevski <[email protected]> * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Predrag Gruevski <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation Co-authored-by: Predrag Gruevski <[email protected]> * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant Co-authored-by: Predrag Gruevski <[email protected]> * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Predrag Gruevski <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation Co-authored-by: Predrag Gruevski <[email protected]> * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant Co-authored-by: Predrag Gruevski <[email protected]> * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Predrag Gruevski <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Kyle Anthony Williams <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Kyle Anthony Williams <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Kyle Anthony Williams <[email protected]>
* Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex<Vertex<'a>> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Kyle Anthony Williams <[email protected]>
Woo! |
I would, but school and research is likely going to be tough, so put them up for grabs. Plan for this summer is to find a way to get paid to contribute to OSS, so this will not be the last time you hear from me for the next 12 months. :) I also will have a ton of free time during winter break, so we should discuss possibilities then, too. |
If there are companies or programs you're interested in applying to, I'd be happy to serve as a reference / write you a recommendation letter / refer you if I know someone there. (Same goes for anyone who consistently contributes quality code to projects I maintain!) I'm a mentor for Google Summer of Code, and from my perspective that program has been good! Not sure how companies / hiring managers feel about it on a resume (as opposed to say a tech company internship) so it's worth looking into that as well. Also happy to work together over winter break — ping me whenever it's a good time to chat about that! |
Adds information about enum discriminants to the schema.
Refs: