Conversation
| let build_env = src_env | ||
| .build_env(ParserBuildEnvArgs::new().mod_name(Some("test_module"))) | ||
| .unwrap(); | ||
| build_env |
There was a problem hiding this comment.
This may still need some work, on how to expose this check in a way that will work with downstream crates that want to use the code_generator. But this stuff is all private still anyways.
The thing to note is that this check still happens in CTParserBuilder so code_generator is not automatically checking for unused keys as a byproduct of code generation.
Hence having to reimplement the checks in these test cases as calls to build_env.check_unused...
There was a problem hiding this comment.
Regardless of whether downstream crates automatically get checking for unused keys,
they can now perform the check themselves to obtain the same results as CTParserBuilder?
There was a problem hiding this comment.
Having thought about it, I'm in the coping stage where it feels like there isn't much the code generator can do about this. It doesn't know at what time all the header values have been resolved, since it gives mutable access to the header via PaserBuildEnv::header_mut(), currently the check_unused_header_keys_for_crate function is also on PaserBuildEnv.
Edit: Oops, disregard the paragraph below. I was looking at the lrlex::codegen, in lrpar::codegen we actually need to mutate the header/mark keys as used after the call to code_generator().
The reason we need it after is because of the inspect_rt callback which we use to implement test_files, it builds a RTParserBuilder, and marks the test_files entry as used.
It is worth noting though that test_files is explicitly marked as experimental in the book here.
Edit2: Given the above perhaps we code do the checking in ParserCodegen::generate it takes a ParserBuildEnv and returns a Result. This provides a place to do the check, but still has some open questions about Header<Location> vs Header<Span>, and which header to perform the check on during codegen.
It looks like it could work if we actually made this a private function, and called it from ParserBuildEnv::code_generator. That is the next point after all header values should have been marked used. Presumably we'd need some way perhaps in ParserBuildEnvArgs to pass it a list of crates to check.
There was a problem hiding this comment.
With various changes, primarily 0ca6955 and 45fca2b
Along with changing the place where we call mark_used and mutate the Header value have allowed these checks to be performed earlier, and largely eliminated the problem of multiple Header values, culminating in allowing this now be done as a side effect of calling code_generator.
So I think this should be fixed. Also, this was the last thing I was struggling with/unhappy about in this PR.
|
It took me a while thinking about how to handle that there are multiple header instances across various crates. I came to the conclusion that it isn't actually a problem, because these The key to this working is that each of these crates is going to be marking and checking the keys for the crates they know in the header they'll check, so we don't really have much of an issue with a key being marked used in one But I don't imagine that is either an obvious as either problem or solution. There's really two ways this could be a problem:
The second case actually will currently fail as we're only calling mark_used in the Edit: I'll work on a fix for this second case, and feel like we can just accept that we won't do the first? So i'm wondering if we just perform the checks e.g. during codegen, and allow inconsistent results if a user tries to call |
|
Marking as ready, because I think I've covered all the issues I can think of as best I can. |
|
I've tried a couple of times to write a sort of high level comment about design issues, but it keeps turning into a textbook/great american novel. This was mostly talking about duplication between e.g. There is something to say though about the mechanisms provided by this patch, which is they currently aren't exposing any of this or making it usable from within In nimbleparse_lsp v2, we're basically calling One of the unforseen problems, which I guess I am encountering now, and didn't anticipate is that the intent was for the public API to kind of not expose the It hadn't really occurred to me how/whether we plan on exposing this via |
|
I'm going to go ahead and mark most of my waffling review comments as resolved for now. It just feels like they're probably more distracting than helpful at this point. |
| /// If the entry is found it marks the key as `used`, for the purposes of `unused_header_keys_for_crate`. | ||
| pub fn header_value_for_crate( | ||
| &mut self, | ||
| crate_name: &str, |
There was a problem hiding this comment.
Dumb question: do we even need to force two variables (crate_name and key_name) on the user? Would key: &str make sense alone?
There was a problem hiding this comment.
We don't, I made the same comment in a review above too, for a different function.
I kind of flipped a coin, and did separate crate names and keys, due to the separate crate name elsewhere
like check_unused_... where the key name isn't passed in at all.
It didn't seem like a strong argument then, happy to change it.
There was a problem hiding this comment.
I think that if we use a single string, we get a nicer API in #667 (comment) too? Warning: I might be wrong.
There was a problem hiding this comment.
At first glance this does look like it improves things?
There was a problem hiding this comment.
Yeah, I think so.
Edit: Moved my other part of the coment elsewhere.
| "lrlex".to_string(), | ||
| ]); | ||
| let unused_keys = ast_validation | ||
| .iter_unused_header_values(crate_prefixes) |
There was a problem hiding this comment.
Hmm, it's worth noting I set this as hidden, so here we're calling a doc(hidden) function in nimbleparse.
Just now, after the fact I'm recalling that nimbleparse is supposed to be more of a working example. In which case we probably want to avoid calling hidden API?
Ideally once codegen is pub, this can be changed to use that and the hidden API will then be confined to being used from codegen.
There was a problem hiding this comment.
Just now, after the fact I'm recalling that nimbleparse is supposed to be more of a working example. In which case we probably want to avoid calling hidden API?
Well, if nimbleparse needs this API, it suggests it should be pub and not mark as ununsed. But if that API is unstable, I agree: it would be better (if possible) to find a stable API for nimbleparse to use.
|
FWIW, I tried to write a relatively short document examining the API from the 3 perspectives of
Tried to keep it short, while being a bit more thorough than what I had done in comments here. https://gist.github.com/ratmice/ff2678c672105766b9805b8eb91097f2 |
|
I think I follow your use cases. A quick note of my personal bias: over time I've come to prefer minimalistic APIs because they tend to allow usecases that didn't occur to me. That isn't always a good thing, of course, but it tends to be my starting point. On that basis, I think the simplest (and dumbest) API only needs two things:
One thing, though, I've taken away from your work is that the prefix idea is a really good one. In particular, it not only gives two crates a de facto way of separating their keys ("use your crate name unless you have a good reason not to") but it gives a de jure way of stopping them treading on each other's toes. In other words Once we've accepted that, it makes sense to define a "prefix" as something like In terms of "mark things as used", I think I'm inclined to push that onto users because one error I think they'll make is "is key X defined? oh, I didn't expect that" but then X would be marked as used automatically. In other words, we're accidentally pushing some policy decisions (that make sense for grmtools) onto users in a way that might confuse them. The good news is that they can very easily work out which keys aren't use with something like: let unused = x.iter_key_prefixes("crate").collect::<HashSet<_>>().difference(HashSet::from(&["b"]);where Sorry for the ramble: let me know what you think! |
|
Totally understand the desire for a simple API, one with just something like One of the things the centralized use checking is aiming to save users from is when they set some setting, and somehow mess it up, that if the setting doesn't get applied for whatever reason, the idea is it should notify them rather than ignore the setting. This is kind of inhibited by the everybody checks their own keys/prefix I think lrlex examples might be the best So it can be the user thinks the setting goes into effect, the So for that reason I thought it was worthwhile to have a centralized checking mechanism, that can point out that Maybe there is some way we can push that onto users with an API like Anyhow, I'm a little bit uncertain whether end-users are going to bother adding these kinds of extra checks if they don't just happen during the build process even if they occasionally find errors. Also sorry for (all of) the rambling too! Edit: I think it's worth explicitly pointing out the somewhat contradictory It seems (as an end-user/build.rs check) we couldn't quite do something like but something to the effect of might, because there is no guarantee that there will be entries for every registered crate? |
I hadn't thought about this. I'm not quite sure what to do here: if a flag is for another tool, should nimbleparse ignore it? My immediate reaction is that I can argue this both ways! |
Ahh, I hadn't really considered any approach besides nimbleparse ignoring it. It at least feels like nimbleparse ignoring actions acts kind of like a precedent that it may ignore parts of the grammar that it is ignorant of or incapable of handling? |
|
So the one thing that came to mind today, is that we turned an unregistered prefix into a warning nimbleparse already just prints warnings, and doesn't behave as though warnings are errors. While the We could also maybe add something like That would give us:
|
Having slept on this, I think this is the better default. I think you're right that having nimbleparse warn about such keys is a good idea, though (at least I think that's what you're suggesting). That will be a nice reminder to users that nimbleparse is doing a "best effort" but if those keys have some semantic influence on the grammar, nimbleparse can't know what to do about it. In terms of the "should grmtools track 'is this key used' in the API", on reflection I think it probably is better if we shove that responsibility onto the users. The existence of |
The issue I suppose I'm struggling with is that nimbleparse and the like are the "incomplete" case which only know a subset of the keys, can only check keys for their known prefix. This one is easily done outside via The other case is where we have e.g. a build.rs and we know the complete list of keys for all crates. Perhaps the right thing to do is start out trying to implement the complete check with the external responsibility for the checks, and see how involved that is and whether it is something we would recommend, or whether the burden justifies the build.rs additions. To do the "complete" check, we definitely need to adjust how I was planning on doing though, since the lists of known keys are spread out across all the crates though. So we can't just easily union them together. Anyhow, I'll give it a go trying to implement both these checks as external checks. With the "complete" check just checking against sets of We can see how that looks and go from there? |
That seems like a good plan to me and we should definitely feel free to reconsider if we realise things are going in the wrong direction! |
|
So, one of the issues I've run across is here is with the So I don't currently see a nice/easy way to compare a Using difference closest I've been able to come up with so far was this kind of helper method, which sadly has to allocate a whole bunch of sets! Edit: apparently If I avoid |
This migrates the checking to be done externally, so removes some test cases. It doesn't yet support "exhaustive" checking. In that it only currently checks for the provided prefixes. Further work is required to ensure that there are no unrecognized prefixes. A bug was also fixed in one of the `IntoIterator` implementations of `MarkMap`.
|
So I think that gives us a rough draft for external checks at least. I'm keeping in my head a list of follow up patches I should write down:
|
| /// let prefixes_set = HashSet::from_iter(build_env.ast_with_validity_info().iter_prefixes()); | ||
| /// let registered_set = HashSet::from_iter(build_env.registered_header_prefixes()); | ||
| /// assert!(prefixes_set.difference(registered_set).next().is_none()) | ||
| /// ``` |
There was a problem hiding this comment.
FWIW, this check shown in the docstring example is one that I would rather see done automatically during codegen rather than externally,
because otherwise people might not call (or notice they should call) register_header_prefix.
and then typo prefixes can fall through.
However that poses a problem, since nimbleparse doesn't know it needs to register_header_prefix for nimbleparse_lsp and all other downstream prefixes. We'd need some alternate mechanism like
%grmtools{
cfgrammar.external_prefixes: ["nimbleparse_lsp"],
}
So that the header itself would register the prefix. It could still then catch typos like lrpr and suggest adding an external prefix:
%grmtools{
lrpr.recoverer: CPCTPlus,
}
I'm not certain how I feel about essentially registering the header prefix from the header itself.
But it seems like it kind of allows us to force these kinds of error checks, so of course I'm curious to hear your thoughts.
I admit that wasn't what I was thinking of, but it's not too bad, and I think we can easily tell the user this is (probably) what they want (rather than providing a helper method ourselves)! |
Yeah, I don't think this is that bad, especially considering that it's mostly crates like |
This is a second attempt at exposing querying of entries defined by downstream crates stored in the
%grmtoolssection.The first attempt was #665, this attempt is extended to allow crates to query for unused keys defined within their namespace. And is overall simpler due to being based on the new
header::Valuetype work done in #666 .