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
Parse impl
blocks, type
and const
declarations
#28
Conversation
d878e62
to
f362893
Compare
I realized that the previous structures couldn't represent some syntax; concretely:
So I just used After some feedback I can also clean up the commits a bit. |
bd0916f
to
c058207
Compare
}; | ||
|
||
let impl_decl = parse_declaration_checked(expr); | ||
assert_debug_snapshot!(impl_decl); |
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.
Have you considered using assert_quote_snapshot
? (defined higher in the file)
I'd lean towards re-using them. Note that venial aims to cover anything that is syntactically valid, even if it's not semantically valid. For instance, this compiles: #[cfg(FALSE)]
const X: i32;
I guess? I haven't really considered it. I think we'd want names that differentiate between the two less ambiguously. I like
I don't remember. Probably not. |
Supports parsing of two impl block schemes: * impl Type { ... } * impl Trait for Type { ... } Each block can contain members of three different categories: * functions * constants * associated types
6b8c348
to
25557a2
Compare
Changes: * Unit tests for mut parameters and lifetimes * Declaration order of function (dependencies above dependents) * Capture all tokens ('fn' keyword, ';' at end)
@PoignardAzur Sorry for the delay. In my initial implementation, a lot of stuff was "half-baked". By doing things a bit more properly, I realized that several things were missing and took the chance to add quite a bit of functionality in the process, while also addressing your feedback 😬 I apologize if this PR got a bit too big. New Features in this PR:
Also added tests for everything. I left the commits split, so it's easier for you to follow the changes. I can squash them to only a handful if you prefer. |
impl
blocksimpl
blocks, type
and const
declarations
Will take a look soon. |
FYI, I'm going to vacation on wednesday (well, kind of, but the gist is I'll have less time for open source) but I'd like to review this before. Ping me tomorrow if I haven't gone back to you by then. |
@PoignardAzur the day is not over yet, but here's a ping, to not push it too far into the evening 😉 |
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.
Overall, this all seems like very high-quality code.
The biggest problems are the incomplete parsers for type and const declarations, but it's always better than no implementation at all, so I wouldn't mind merging it now.
Do you want me to give you write access to the repository? Since I don't intend to maintain the project in the coming months, you might want a tighter feedback loop. And while I made a lot of small nitpicks, overall the quality of your contributions seems high enough I don't need to double-check everything.
src/parse_fn.rs
Outdated
/// Panics when the following tokens do not constitute a function definition, with one exception: | ||
/// when `const` is followed by an identifier which is not `fn`, then `None` is returned. This is to | ||
/// allow fallback to a constant declaration (both can begin with the `const` token). | ||
pub(crate) fn try_consume_fn( |
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 function should be called consume_fn
.
Overall, the naming convention so far has been that consume_xxx
functions in venial return None if the syntax item is absent, and panic if the beginning of the syntax item is present but the expect continuation isn't.
Eg: consume_where_clause
will return None if it doesn't find a where
token, but will panic if it gets where "hello world"
.
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.
Done.
src/parse_impl.rs
Outdated
} | ||
} | ||
|
||
pub(crate) fn parse_impl_members(token_group: Group) -> (Vec<ImplMember>, GroupSpan) { |
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 misses inner attributes, eg:
impl Foobar {
#![hello]
}
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.
Implemented.
Also renamed tk_hashbang
to tk_hash
, which is more correct.
src/tests.rs
Outdated
fn parse_fn_mut_param() { | ||
let func = parse_declaration(quote! { | ||
fn prototype(a: i32, mut b: f32) -> String; | ||
}) | ||
.unwrap(); | ||
|
||
assert_debug_snapshot!(func); | ||
} |
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 nitpick: I'd rather these tests be a bit more consistent with existing tests:
- Use assert_quote_snapshot
- Use parse_declaration_checked
- Use
quote!
with parentheses - Add a consume_generic_args_checked function that does the same thing as parse_declaration_checked
Though none of these are blockers, it's more for the code aesthetic ^^
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.
Done, except assert_quote_snapshot
, here I wasn't sure what exactly you meant.
This is already part of parse_declaration_checked
, no? assert_debug_snapshot
seems more meaningful for the "data model" representation (and not the token one). Unless this is very important, I'd prefer if this one could be postponed, otherwise this PR is never-ending 🙂
src/parse_impl.rs
Outdated
let tk_equals = match tokens.next() { | ||
Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => punct, | ||
_ => panic!("cannot parse constant"), | ||
}; | ||
|
||
let value_tokens = consume_stuff_until( | ||
tokens, | ||
|tt| matches!(tt, TokenTree::Punct(punct) if punct.as_char() == ';'), | ||
true, | ||
); |
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.
Constants without = xxx
are syntactically valid and should be accepted.
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.
Implemented, the =
and value are now optional:
pub struct Constant {
pub attributes: Vec<Attribute>,
pub vis_marker: Option<VisMarker>,
pub tk_const: Ident,
pub name: Ident,
pub tk_colon: Punct,
pub ty: TyExpr,
/// The '=' sign is optional; constants without initializer are syntactically valid.
pub tk_equals: Option<Punct>,
/// The initializer value is optional; constants without initializer are syntactically valid.
pub initializer: Option<ValueExpr>,
pub tk_semicolon: Punct,
}
Also added a test for this case.
src/parse_impl.rs
Outdated
pub(crate) fn consume_assoc_ty( | ||
tokens: &mut TokenIter, | ||
attributes: Vec<Attribute>, | ||
vis_marker: Option<VisMarker>, | ||
) -> TyDefinition { |
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 should be called consume_type_definition
. If called from the top level, it might parse a type alias, not an associated type.
(Also, for consistency, it should arguably return Option)
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.
Renamed and optionalized 🙂
let tk_equals = match tokens.next() { | ||
Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => punct, | ||
_ => panic!("cannot parse associated type"), | ||
}; |
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 assumes that every type definition is of the form type Foo = some::Stuff;
. But:
- Type definitions may have bounds (eg
type Foo: Clone;
) - Type definitions may have generic parameters (eg
type Item<Foo> = Bar;
) - Type definitions may have where-clauses (eg
type Foo where T: Debug = Bar;
) - Type definitions are not required to have a type (eg
type Foo;
)
See the full definition there: https://doc.rust-lang.org/reference/items/type-aliases.html
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.
If possible, I'd like to defer this to a follow-up PR about traits, then we could have one TyDefinition
which supports both.
I agree here, but maybe I should highlight missing parts (e.g. as
Thanks a lot for the compliment and the trust, it's appreciated 🙂 Write access might be nice for quicker integration, thanks for offering. Even though, I have to say your feedback in PRs has also been very valuable; I still need to understand some of the conventions and practices of venial. I don't know if you plan on reviewing things after some months, but I guess you could also directly clean up the Maybe more generally, would that be more of a hiatus for a couple of months, or are you generally not sure about long-term plans? Are there more people involved or potentially interested in venial's maintenance? With godot-rust and a couple other open-source projects, I could probably implement some things every now and then, but realistically I don't see how I can spend enough time to help review PRs etc. I do see a lot of potential for a lightweight parser though, and know some people who think similarly. |
Yeah, that's good practice.
I still plan on reviewing incoming PRs (there's few enough it's not exactly a drag).
That's the idea. 😁
I don't really have long-term plans for venial. As I've said elsewhere, the initial benchmarks have been somewhat disappointing (it's faster than syn, but not so much that I'd expect mass adoption). I mostly see the project as an informative experiment. My plan is to make a 1.0 release at some point this year, with an attached post-mortem, and then I'll only fix issues and merge PRs.
I'm not trying to dump the project in your lap ^^. There aren't too many people involved in the project (as you can see from the commit history). If you know people who'd be interested in getting involved, sure, that would be helpful, but I don't really expect it. Again, I don't plan on archiving the project. If people submit PRs, I'll review them. I just don't plan to have a very active role in the future. (And honestly, given this was only side project I tried to do in a week to distract myself from Druid, I'm pretty impressed with how far it has come) |
No worries, that's not how I understood it, just wanted to make sure you're not disappointed if my contributions decrease in frequency over time 😉
In godot-rust, syn is a major compile time offender (total compile time 206s fresh, of which syn is 18%): Now I won't rewrite that all to venial, so it's hard to tell how much difference there really is. But what I am going to try is use venial for the next version of godot-rust (GDExtension binding) and see how far I can push it. I'm a huge fan of lightweight libraries -- could be that I won't reach my goals in the end, but I think venial deserves to get a chance in a larger project 🚀 syn is an extremely powerful and versatile library, but two things bug me in particular:
A syn 2.0 could possibly address 1) by modularizing the crates, and 2) by providing lazy evaluation for rarely used items. But this is a mostly speculative idea without benchmarking. venial is a good trade-off here. |
Also added .gitignore for IDEs and very basic naming guidelines
I addressed your feedback:
The Another thought -- once venial supports a lot more cases than just basic structs and some functions, should we advertise it a bit (Twitter, Reddit etc.)? Maybe after a |
Also renames tk_hashbang to tk_hash (the `#` token).
Yeah, that's definitely the plan! |
Do you think we can merge this PR as it is now? 🙂 |
Done. |
That comment you added makes me think it might be worth adding an |
That sounds like a good idea! I haven't seen the |
I haven't seen it in the wild too much (or at all?), I'm referring to the kind of documentation described in this post: https://matklad.github.io/2021/02/06/ARCHITECTURE.md.html I see it as being a bit different from |
This pull request takes #27 as a base.
Supports parsing of two impl block schemes:
impl Type { ... }
impl Trait for Type { ... }
Each block can contain members of three different categories:
Examples:
There are a few open questions on my side:
I plan to also add support for
trait
s. Regarding some of the new API types I added, there's the option to reuse them (forcing users to unwrap options) or to make dedicated, type-safe separate ones fortrait
andimpl
contexts. What is your preference? This would also affect the naming (e.g. I'm not happy withTypeDefinition
).Examples:
const CONSTANT: i8;
Impl:
const CONSTANT: i8 = value;
either we have 1 type representing both, with optional
=
andvalue
, or 2 separate types.type AssocTy: Clone;
-- can have bounds, but (at the moment) no default type initializersImpl:
type AssocTy = MyType;
-- must have type initializers, no boundsI was thinking to add
GenericArg
/GenericArgList
to represent<'a, T, U, N>
-- in addition toGenericParam/GenericParamList
for<'a: 'static, T, U: Clone, const N: usize>
. Would that make sense?If yes, we could probably remove
InlineGenericArgs
and directly allow to convert from parameters (unless you say we can't afford that copy).Is there a reason why the
Debug
impl forGenericParam
does not include thetk_prefix
? Might be good to check if it's a lifetime, a const or a type.venial/src/types.rs
Lines 486 to 493 in b490dd7
There might be some room to make the naming more consistent (e.g.
FunctionParameter
vs.GenericParam
) as well as other smaller refactorings, but it probably makes more sense to work out a proposal to parsetrait
first 🙂