You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Incorrect results when using ArgGroup + defaultValue + split + List/Set #2349
When using the features ArgsGroup along with defaultValue and split, the value reported from the second group is not correct as per the arguments provided.
NOTE TO SELF:
With ArgGroups, the same ArgSpec object is used as a template to initialize multiple user objects.
This means that the logic to determine whether an ArgSpec has been seen by the parser (and therefore, the value is a command line value, not a default value), cannot rely on the ArgSpec instance. But it does.
// Interpreter::applyValuesToCollectionFieldif (collection == null || !initialized.contains(argSpec)) { // <-- // for the --type=TWO group, the `initialized` set contains the --actions ArgSpec. // Therefore, the binding will not be initialized with an empty collection, // and the command line value is added to the collection with the default value(s)...
NOTE TO SELF:
Index: src/main/java/picocli/CommandLine.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java (date 1744359357420)
@@ -14339,6 +14339,9 @@
if (tracer.isInfo()) { tracer.info(traceMessage, argSpec.toString(), String.valueOf(oldValue), displayVal, argDescription, argSpec.scopeString()); }
int pos = getPosition(argSpec);
argSpec.setValue(newValue);
+ if (argSpec.group() != null && !parseResultBuilder.isInitializingDefaultValues && argSpec.valueIsDefaultValue) { // #2349 bugfix for ArgGroup / Set / default value
+ argSpec.valueIsDefaultValue = false;
+ }
parseResultBuilder.addOriginalStringValue(argSpec, actualValue);// #279 track empty string value if no command line argument was consumed
parseResultBuilder.addStringValue(argSpec, actualValue);
parseResultBuilder.addTypedValues(argSpec, pos, newValue);
@@ -14355,10 +14358,12 @@
if (argSpec.auxiliaryTypes().length < 2) { throw new ParameterException(CommandLine.this, argSpec.toString() + " needs two types (one for the map key, one for the value) but only has " + argSpec.auxiliaryTypes().length + " types configured.",argSpec, null); }
Map<Object, Object> map = argSpec.getValue();
Tracer tracer = CommandLine.tracer();
- if (map == null || !initialized.contains(argSpec)) {
+ boolean shouldReset = argSpec.group() != null && !parseResultBuilder.isInitializingDefaultValues && argSpec.valueIsDefaultValue; // #2349 bugfix for ArgGroup / Set / default value
+ if (map == null || !initialized.contains(argSpec) || shouldReset) {
tracer.debug("Initializing binding for %s on %s with empty %s", optionDescription("", argSpec, 0), argSpec.scopeString(), argSpec.type().getSimpleName());
map = createMap(argSpec.type()); // map class
argSpec.setValue(map);
+ argSpec.valueIsDefaultValue = false;
}
addToInitialized(argSpec, initialized);
int originalSize = map.size();
@@ -14541,7 +14546,8 @@
int pos = getPosition(argSpec);
List<Object> converted = consumeArguments(argSpec, negated, lookBehind, alreadyUnquoted, alreadyUnquoted, arity, args, argDescription);
List<Object> newValues = new ArrayList<Object>();
- if (initialized.contains(argSpec)) { // existing values are default values if initialized does NOT contain argsSpec
+ boolean shouldReset = argSpec.group() != null && !parseResultBuilder.isInitializingDefaultValues && argSpec.valueIsDefaultValue; // #2349 bugfix for ArgGroup / Set / default value
+ if (initialized.contains(argSpec) || shouldReset) { // existing values are default values if initialized does NOT contain argsSpec
for (int i = 0; i < length; i++) {
newValues.add(Array.get(existing, i)); // keep non-default values
}
@@ -14559,6 +14565,9 @@
Array.set(array, i, newValues.get(i));
}
argSpec.setValue(array);
+ if (shouldReset) {
+ argSpec.valueIsDefaultValue = false;
+ }
parseResultBuilder.add(argSpec, pos);
return converted.size(); // return how many args were consumed
}
@@ -14574,11 +14583,12 @@
Collection<Object> collection = argSpec.getValue();
int pos = getPosition(argSpec);
List<Object> converted = consumeArguments(argSpec, negated, lookBehind, alreadyUnquoted, alreadyUnquoted, arity, args, argDescription);
- boolean shouldReset = argSpec.group() != null && !parseResultBuilder.isInitializingDefaultValues && argSpec.valueIsDefaultValue;
- if (collection == null || !initialized.contains(argSpec) ) {
+ boolean shouldReset = argSpec.group() != null && !parseResultBuilder.isInitializingDefaultValues && argSpec.valueIsDefaultValue; // #2349 bugfix for ArgGroup / Set / default value
+ if (collection == null || !initialized.contains(argSpec) || shouldReset) {
tracer().debug("Initializing binding for %s on %s with empty %s", optionDescription("", argSpec, 0), argSpec.scopeString(), argSpec.type().getSimpleName());
collection = createCollection(argSpec.type(), argSpec.auxiliaryTypes()); // collection type, element type
argSpec.setValue(collection);
+ argSpec.valueIsDefaultValue = false;
}
addToInitialized(argSpec, initialized);
for (Object element : converted) {
Activity
remkop commentedon Nov 4, 2024
Thank you for raising this!
I'll have to look with
-Dpicocli.trace=INFO(or =DEBUG) to see what is happening.remkop commentedon Dec 20, 2024
Note to self:
[#2349] Added test to reproduce the issue.
remkop commentedon Apr 11, 2025
NOTE TO SELF:
With ArgGroups, the same
ArgSpecobject is used as a template to initialize multiple user objects.This means that the logic to determine whether an
ArgSpechas been seen by the parser (and therefore, the value is a command line value, not a default value), cannot rely on theArgSpecinstance. But it does.NOTE TO SELF:
[remkop#2349] Fix incorrect results when using `ArgGroup` + defaultVa…