Skip to content

Commit bc4de58

Browse files
jasmith-hsclaude
andcommitted
Auto-convert Integer to Long in set filters when feature is enabled
Integer/Long mismatches are common when set elements come from different sources (e.g. one side from a Java Long field, the other from a Jinja literal that resolves to Integer). These are semantically equivalent but cause empty intersections without any actionable warning. The new INTEGER_SET_TO_LONG_CONVERSION feature widens the Integer set to Long before performing the operation so the result is correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 90af49e commit bc4de58

3 files changed

Lines changed: 118 additions & 7 deletions

File tree

src/main/java/com/hubspot/jinjava/features/BuiltInFeatures.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public interface BuiltInFeatures {
88
String IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS =
99
"IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS";
1010
String OUTPUT_UNDEFINED_VARIABLES_ERROR = "OUTPUT_UNDEFINED_VARIABLES_ERROR";
11+
String INTEGER_SET_TO_LONG_CONVERSION = "INTEGER_SET_TO_LONG_CONVERSION";
1112
}

src/main/java/com/hubspot/jinjava/lib/filter/AbstractSetFilter.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.hubspot.jinjava.lib.filter;
22

3+
import com.hubspot.jinjava.features.BuiltInFeatures;
34
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
45
import com.hubspot.jinjava.interpret.TemplateError;
56
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
@@ -43,7 +44,27 @@ public Object filter(
4344
Set<Object> varSet = objectToSet(var);
4445
Set<Object> argSet = objectToSet(parseArgs(interpreter, args));
4546

46-
attachMismatchedTypesWarning(interpreter, varSet, argSet);
47+
if (!varSet.isEmpty() && !argSet.isEmpty()) {
48+
Object oneVar = varSet.iterator().next();
49+
Object oneArg = argSet.iterator().next();
50+
51+
boolean featureActive = interpreter
52+
.getConfig()
53+
.getFeatures()
54+
.isActive(
55+
BuiltInFeatures.INTEGER_SET_TO_LONG_CONVERSION,
56+
interpreter.getContext()
57+
);
58+
if (featureActive) {
59+
if (oneVar instanceof Integer && oneArg instanceof Long) {
60+
varSet = convertIntegersToLongs(varSet);
61+
} else if (oneArg instanceof Integer && oneVar instanceof Long) {
62+
argSet = convertIntegersToLongs(argSet);
63+
}
64+
}
65+
66+
attachMismatchedTypesWarning(interpreter, varSet, argSet, oneVar, oneArg);
67+
}
4768

4869
return filter(varSet, argSet);
4970
}
@@ -55,17 +76,31 @@ protected void attachMismatchedTypesWarning(
5576
Set<Object> varSet,
5677
Set<Object> argSet
5778
) {
58-
boolean hasAtLeastOneSetEmpty = varSet.isEmpty() || argSet.isEmpty();
59-
if (hasAtLeastOneSetEmpty) {
79+
if (varSet.isEmpty() || argSet.isEmpty()) {
6080
return;
6181
}
82+
attachMismatchedTypesWarning(
83+
interpreter,
84+
varSet,
85+
argSet,
86+
varSet.iterator().next(),
87+
argSet.iterator().next()
88+
);
89+
}
6290

63-
boolean areMatchedElementTypes = getTypeOfSetElements(varSet)
64-
.equals(getTypeOfSetElements(argSet));
65-
if (areMatchedElementTypes) {
91+
private void attachMismatchedTypesWarning(
92+
JinjavaInterpreter interpreter,
93+
Set<Object> varSet,
94+
Set<Object> argSet,
95+
Object oneVarObj,
96+
Object oneArgObj
97+
) {
98+
if (getTypeOfSetElements(varSet).equals(getTypeOfSetElements(argSet))) {
99+
return;
100+
}
101+
if (potentiallyConvertibleNumbers(oneVarObj, oneArgObj)) {
66102
return;
67103
}
68-
69104
interpreter.addError(
70105
new TemplateError(
71106
TemplateError.ErrorType.WARNING,
@@ -84,6 +119,25 @@ protected void attachMismatchedTypesWarning(
84119
);
85120
}
86121

122+
private boolean potentiallyConvertibleNumbers(Object oneVarObj, Object oneArgObj) {
123+
return (
124+
(oneArgObj instanceof Integer && oneVarObj instanceof Long) ||
125+
(oneVarObj instanceof Integer && oneArgObj instanceof Long)
126+
);
127+
}
128+
129+
private Set<Object> convertIntegersToLongs(Set<Object> set) {
130+
Set<Object> result = new LinkedHashSet<>();
131+
for (Object element : set) {
132+
if (element instanceof Integer integer) {
133+
result.add(integer.longValue());
134+
} else {
135+
result.add(element);
136+
}
137+
}
138+
return result;
139+
}
140+
87141
private String getTypeOfSetElements(Set<Object> set) {
88142
return TypeFunction.type(set.iterator().next());
89143
}

src/test/java/com/hubspot/jinjava/lib/filter/AbstractSetFilterTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44

55
import com.hubspot.jinjava.BaseJinjavaTest;
6+
import com.hubspot.jinjava.Jinjava;
7+
import com.hubspot.jinjava.features.BuiltInFeatures;
8+
import com.hubspot.jinjava.features.FeatureConfig;
9+
import com.hubspot.jinjava.features.FeatureStrategies;
610
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
11+
import com.hubspot.jinjava.interpret.RenderResult;
712
import com.hubspot.jinjava.interpret.TemplateError;
13+
import java.util.HashMap;
814
import java.util.List;
15+
import java.util.Map;
916
import java.util.Set;
1017
import org.junit.Before;
1118
import org.junit.Test;
@@ -80,4 +87,53 @@ public void itThrowsWarningOnMismatchTypes() {
8087
"Mismatched Types: input set has elements of type 'long' but arg set has elements of type 'str'. Use |map filter to convert sets to the same type for filter to work correctly."
8188
);
8289
}
90+
91+
@Test
92+
public void itDoesNotThrowWarningOnIntegerLongMismatch() {
93+
JinjavaInterpreter interpreter = jinjava.newInterpreter();
94+
95+
Set<Object> varSet = concreteSetFilter.objectToSet(new Long[] { 1L, 2L, 3L });
96+
Set<Object> argSet = concreteSetFilter.objectToSet(new Integer[] { 1, 2, 3 });
97+
concreteSetFilter.attachMismatchedTypesWarning(interpreter, varSet, argSet);
98+
99+
assertThat(interpreter.getErrors()).isEmpty();
100+
}
101+
102+
@Test
103+
public void itConvertsIntegerToLongWhenFeatureActive() {
104+
Jinjava jinjavaWithFeature = new Jinjava(
105+
BaseJinjavaTest
106+
.newConfigBuilder()
107+
.withFeatureConfig(
108+
FeatureConfig
109+
.newBuilder()
110+
.add(BuiltInFeatures.INTEGER_SET_TO_LONG_CONVERSION, FeatureStrategies.ACTIVE)
111+
.build()
112+
)
113+
.build()
114+
);
115+
116+
Map<String, Object> vars = new HashMap<>();
117+
vars.put("longList", new Long[] { 1L, 2L, 3L });
118+
vars.put("intList", new Integer[] { 2, 3, 4 });
119+
120+
String result = jinjavaWithFeature.render("{{ longList|intersect(intList) }}", vars);
121+
122+
assertThat(result).isEqualTo("[2, 3]");
123+
}
124+
125+
@Test
126+
public void itDoesNotConvertWhenFeatureInactive() {
127+
Map<String, Object> vars = new HashMap<>();
128+
vars.put("longList", new Long[] { 1L, 2L, 3L });
129+
vars.put("intList", new Integer[] { 2, 3, 4 });
130+
131+
RenderResult renderResult = jinjava.renderForResult(
132+
"{{ longList|intersect(intList) }}",
133+
vars
134+
);
135+
136+
assertThat(renderResult.getOutput()).isEqualTo("[]");
137+
assertThat(renderResult.getErrors()).isEmpty();
138+
}
83139
}

0 commit comments

Comments
 (0)