feat: Specify different separators for each children group of a commutative parent #374
No reviewers
Labels
No labels
Compat/Breaking
Kind
Bad merge
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
New language
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: mergiraf/mergiraf#374
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "wetneb/mergiraf:279-separator-per-children-group"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Here's more work on getting whitespace right :)
Fixes #251. Fixes #279.
I have also opened #373 outlining a different approach to the problem. I think we need both solutions: the one proposed here and the one outlined there, as none of them will fix all cases:
It's amazing that you were able to implement this with so few changes outside
lang_profile.rs
:)I have a couple of questions regarding the implementation though
@ -235,1 +239,3 @@
.any(|group| group.node_types.is_superset(node_types))
/// If so, return the separator to use when inserting two nodes
/// in the same place.
pub(crate) fn children_can_commute(&self, node_types: &HashSet<&str>) -> Option<&'static str> {
Maybe rename this to something like
commutative_child_separator
? That makes the role of this method a bit clearer imo -- "if the children can commute, here's the separator, otherwise I'll return aNone
"@ -236,0 +239,4 @@
/// If so, return the separator to use when inserting two nodes
/// in the same place.
pub(crate) fn children_can_commute(&self, node_types: &HashSet<&str>) -> Option<&'static str> {
self.children_groups
This is very confusing imo...
I first simplified this by replacing
then_some().or()
with just an if-else:After that I thought I could extract
.or(Some(self.separator))
out offind_map
since it's applied to every element anyway, but then I realized that that's actually not completely true -- apparently we:node_types
are all in theChildrenGroup
we're looking atChildrenGroup
specifies if it does specify one, or the default one for the whole commutative group if it doesn'tSo maybe it would be nice to add a comment along the lines of what I've described above (if I understood everything correctly).
Thinking about this a bit more, why do we unconditionally return
Some(self.separator)
in theself.children_groups.is_empty()
case? Granted, that seems to have been the case before as well, but wouldn't we need thatnode_types
actually are all part of this commutative group? Or is that an implicit assumption that this method makes?Yes your understanding is correct, I'll add some comments to confirm.
@ -243,1 +259,4 @@
pub node_types: HashSet<&'static str>,
/// An optional separator specific to this children group,
/// better suited than the one from the commutative parent.
/// It must only differ from the separator of the parent up to
maybe add this as an assertion in
CommutativeGroup::restricted_to
?@ -448,2 +456,3 @@
CommutativeParent::new("field_declaration_list", "{\n", "\n", "\n}\n")
.restricted_to_groups(&[&["field_declaration"], &["function_definition"]]),
.restricted_to(vec![
ChildrenGroup::new(&["field_declaration"]),
I've been wanting to turn those slices into arrays (to avoid copying later), but it was impossible back then since you can't store arrays of different sizes in the same collection (the outer slice in this case). But now that we wrap each inner slice in
ChildrenGroup::new
/ChildrenGroup::with_separator
, we should actually be able to. And the outer slice (which has now become a vec), should be able to be turned into an array as well.None of that should be a part of this PR of course -- I'll try to remember to look into that after we've merged this
Nope, this still is not possible --
CommutativeGroup::restricted_to_groups
still operates on slices of slices, but if we were to replaceChildrenGroup
methods to accept arrays, we'd need to pass slices of arrays torestricted_to_groups
, which won't work (in general) since slices of differently-sized arrays are not allowed. We could of course instead introduce new, array-based alternatives toChildrenGroup::{new,with_separator}
, but that's really not worth it@ -681,2 +681,3 @@
return Err("The children are not allowed to commute".to_string());
}
};
let trimmed_sep = raw_separator.trim();
Wouldn't you need to use this
trimmed_sep
above, when constructing the leader vecs? Or is it not relevant there for some reason?This line is superfluous indeed since we assume that the trimmed versions of both separators are the same. I'll remove it.
@ -228,1 +229,4 @@
/// Restrict a commutative parent to some children groups, possibly with their own separators
pub(crate) fn restricted_to(self, children_groups: Vec<ChildrenGroup>) -> Self {
#[cfg(debug_assertions)]
just fyi: this could also be achieved by replacing the
assert!
below withdebug_assert!
-- the then empty loop will definitely get optimized away. But this works just as well of courseAh ok, I wasn't sure… I guess it also makes it clear to the reader that the whole check is done only in debug mode.
Yeah, also wanted to mention that. Since the two approaches are otherwise identical, let's go with the current, more readable one
@ -229,0 +233,4 @@
{
for children_group in &children_groups {
if let Some(specific_separator) = children_group.separator {
assert!(
consider using
assert_eq!
instead?3b629eaa95
tofb27ef61a6
@ -678,3 +678,3 @@
.map(Leader::grammar_name)
.collect();
if !commutative_parent.children_can_commute(&child_types) {
let Some(raw_separator) = commutative_parent.child_separator(&child_types) else {
I kind of have the same question I had earlier regarding
trimmed_sep
, but withraw_separator
now -- why is it okay that above, we get it simply ascommutative_parent.separator
, but then go on to the lengths of callingchild_separator
? Well that's probably because we don't actually use the firstraw_separator
anywhere -- instead we justtrim
it right away.I see that there are a couple more places around the codebase where we still access
commutative_parent.separator
directly, but all of them, again,trim
it before using. So maybe it'd be a good idea to hide theseparator
field (so remove itspub
)? That way, we'd either need to provide the children to get the separator that they want, or be content with the trimmed separator, since that one will be the same for the parent and the children. The second option could be realized using a newCommutativeParent::trimmed_separator()
methodSounds like an excellent idea, I'll do that.
@ -229,0 +237,4 @@
assert_eq!(
specific_separator.trim(),
self.separator.trim(),
"Children group separator '{}' inconsistent with parent separator '{}' in commutative parent '{}'",
Formatting a string with
{:?}
uses the Debug representation, which, among others, escapes the special characters, and enclosed the whole string in double quotes. So I think that could replace both theescape_separator
helper and the single quotes in the format string?Fantastic, that's much better of course!