Skip to content
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

FIX: Resolve Alias Recursion Issue by Preventing Duplicate Rule Application #1649

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from

Conversation

cmfrydos
Copy link
Contributor

Closes #1647

Previously, the check was only to ensure that the right side of an alias rule does not repeat, which is not effective for endlessly expanding aliases such as buy="buy -l". Now, the implementation ensures a single alias rule is not recursively applied multiple times by remembering and checking the entire rule.

Updates expected values in tests; this change may disrupt existing setups with endless recursive aliases. Please verify if the new expected values align with your expectations. It would be possible to avoid applying the last substitution if it would result in infinite recursion, but this approach would not comply with the POSIX standard.

@cmfrydos
Copy link
Contributor Author

Previously, the check was only to ensure that the right side of an alias rule does not repeat.

Actually, it was more flawed than that, but it's hard to put into words. Whenever it processed a rule like a=b, it checked whether it already had seen the left side a, but then 'remembers' the ride side b. This has the same effect, that when the right side expands, i.e. a=a b, it will run into endless recursion, since it always checks whether it had seen a, but then remember a b, so it will never see just a on the right side and thus apply the rule endlessly.

@cmfrydos
Copy link
Contributor Author

Updates expected values in tests

Basically for rules like (a=b, b=a) previously applyAlias('a') returned b, now it returns a, since it stopps as soon as it tries to apply the first rules a second time.

Previously it did following shenanigans (see post above):
input: a, rule: a=b, visited: {}, check a in {}, remember {b}, output b
input: b, rule: b=a, visited: {b}, check b in {b}, stop, output input b

@cmfrydos
Copy link
Contributor Author

The new expected values also correspond to the behavior observed with aliases under Ubuntu:
grafik

@cmfrydos
Copy link
Contributor Author

And here a screenshot that the alias mentioned in #1647 now works as expected, regardless of whether its useful:
grafik

Copy link
Collaborator

@d0sboots d0sboots left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests!

test/jest/Alias/Alias.test.ts Outdated Show resolved Hide resolved
src/Alias.ts Outdated Show resolved Hide resolved
@ficocelliguy
Copy link
Contributor

Good catch on this. Nice test coverage, too.

@cmfrydos
Copy link
Contributor Author

I've incorporated the feedback from d0sboots. Please let me know if there’s anything else to adjust!

if (localAlias && !currentlyProcessingAliases.includes(localrule)) {
const appliedAlias = applyAliases(localAlias, depth + 1, [localrule, ...currentlyProcessingAliases]);
if (localAlias && !currentlyProcessingAliases.has(localrule)) {
const appliedAlias = applyAliases(localAlias, depth + 1, new Set([localrule, ...currentlyProcessingAliases]));
Copy link
Collaborator

@d0sboots d0sboots Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wanted to just merge, but I can't let this go... XD

Don't create a brand-new Set, that will be the same O(N^2) behavior as the array you just replaced. Add the item to the set you have.

Edit: For this to work properly (because there are other checks that happen after), you also have to remove it after you recurse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think this through, I'm just in love with immutables. But you're right ofc that this is a little expensive.
For the global rules you have to copy, since a global should be able to be applied multiple times. For the local one it might suffice to just add it.

Copy link
Collaborator

@d0sboots d0sboots Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really can't stand to do mutable stuff, I'm fine with you changing it back to an array. There's no point in doing something more verbose if you're going to be N^2 anyway, and most people won't have lots of deep aliases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh don't merge, there is another bug that will make local rules be applied globally I believe. Sorry that I haven't thought this through carefully before. I was just focused on this single bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be hard to fix though, but I'd like to write some more tests to catch it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based attitude

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a point in doing it more verbose: readability. Sets, in contrasts to Arrays, are unique and often used for lookup. So it helps with getting the codes intention.

I think mutating the set for the local aliases is totally fine, but copying is needed for the global ones. I don't believe you can circumvent that.

I will probably finish this tomorrow. It is easy to mess up.
After global rules are applied, subsequent calls to applyAlias should just consider global rules with the exception of a global rule modifying the first token/command. I believe the function might need some restructuring of the execution flow to handle all edge cases.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of how you end up restructuring, the following should work for mutation without copying: Add the key to the set immediately before recursion, and remove it immediately after.

A quick sketch of why this works: You abort if the key was already present, so there's never an issue of double-removing a key. Thus, the removal always properly reverts to the prior state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's a great approach. I'll probably use that, but then also "unify" the application of local and global rules inside one loop / reduce, to avoid certain edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

recursive alias causes error
3 participants