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

[Bug] Merging of sibling nodes with same name is faulty #32

Open
Marcono1234 opened this issue Oct 9, 2018 · 3 comments
Open

[Bug] Merging of sibling nodes with same name is faulty #32

Marcono1234 opened this issue Oct 9, 2018 · 3 comments
Labels

Comments

@Marcono1234
Copy link

It appears it is intended that sibling nodes with the same name exist (see this comment). However the way they are treated is in my opinion rather unintuitive.

The method CommandNode.addChild(CommandNode<S>) replaces the command of the existing node and adds the children of the new node to the existing one. This is, at least from my point of view, not the wanted behavior since

  • The old command is lost
  • Other attributes of the new node like the requirement are lost

It might be best to either remove this merging and allow siblings with the same name, or to prevent them when building.

Example code

CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
    
dispatcher.register(
    literal("foo")
    .then(
        argument("bar", word())
        .executes(c -> {
            System.out.println("string");
            return 1;
        })
    )
    .then(
        argument("bar", bool())
        .executes(c -> {
            System.out.println("bool");
            return 1;
        })
    )
);

dispatcher.execute("foo not_a_bool", null);
@Dinnerbone
Copy link
Contributor

Names must be unique, so we can't allow multiple with the same name. It's used in a variety of places as a key, such as pathing in the tree, serialization, or simply getting values out of the context.

Merging is supported as a design principle (which I really must write down somewhere!). It must be supported to "enhance" a command after it has already been registered, to allow mods to easily extend commands in Minecraft (and I'm sure other applications/games will find that useful too).

That said, merging must be made smarter - I totally agree with this. We should probably do some sanity checking when a merge happens, and throw an exception if it's nonsense - if anything other than the children changed. For example in your code, the second .then(argument("bar"), bool()) should throw an exception as the very type of the argument has changed, and that should not be allowed.

@Marcono1234
Copy link
Author

What do you think about having separate methods for this modification instead? For example ArgumentCommand could have the methods

  • addArgument(ArgumentType<T>)
    Adds the given argument type, which has to have the same type as the argument node. Argument types would then be stored in a list and the respective methods would go through the other arguments in this list if one fails.
  • setArguments(List<ArgumentType<T>>)
    Replaces all currently set argument types.

Methods like these might be less suprising than runtime exceptions which are thrown when the merge fails and they already define restrictions during compile time. Additionally this implementation might be less error prone since there are a lot of cases you have to consider when determining if a merge is valid.

@JesFot
Copy link

JesFot commented Dec 1, 2019

A solution I can think of would be :

  • Allow merging literal arguments with the same name
  • Allow merging required arguments with the same name and same type
  • Prevent merging of required arguments of different types

Exemple :

// Case 1 :
dispatcher.register(
    literal("foo")
    .then(
        literal("bar")
        .executes(c -> {
            System.out.println("string");
            return 1;
        })
    )
    .then(
        literal("bar") // Same name, literal -> Merge
        .then(literal("baz"))
    )
);
// Case 2 :
dispatcher.register(
    literal("foo")
    .then(
        argument("bar", word())
        .executes(c -> {
            System.out.println("string");
            return 1;
        })
    )
    .then(
        argument("bar", word()) // Same name, same type -> Merge
        .then(literal("baz"))
    )
);
// Case 3 :
dispatcher.register(
    literal("foo")
    .then(
        argument("bar", word())
        .executes(c -> {
            System.out.println("string");
            return 1;
        })
    )
    .then(
        argument("bar", bool()) // Same name, different type -> Exception
        .executes(c -> {
            System.out.println("bool");
            return 1;
        })
    )
);

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

No branches or pull requests

3 participants