Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -357,12 +358,19 @@ public static SoyDict mapToLegacyObjectMap(SoyMap map) {
}

/** Returns the numeric maximum of the two arguments. */
public static NumberData max(SoyValue arg0, SoyValue arg1) {
if (arg0 instanceof IntegerData && arg1 instanceof IntegerData) {
return IntegerData.forValue(Math.max(arg0.longValue(), arg1.longValue()));
} else {
return FloatData.forValue(Math.max(arg0.floatValue(), arg1.floatValue()));
public static NumberData max(List<SoyValue> args) {
if (args.isEmpty()) {
return FloatData.forValue(Double.NEGATIVE_INFINITY);
}

Optional<Double> maxVal = args.stream().map(SoyValue::numberValue).max(Double::compare);
// Return IntegerData if all arguments are IntegerData.
if (args.stream().filter(v -> v instanceof IntegerData).count() == args.size()) {
return IntegerData.forValue(maxVal.get().longValue());
}
return maxVal
.map(FloatData::forValue)
.orElseThrow(() -> new IllegalArgumentException("Invalid arguments for max function."));
}

/** Returns the numeric minimum of the two arguments. */
Expand Down
12 changes: 5 additions & 7 deletions java/src/com/google/template/soy/basicfunctions/MaxFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.google.template.soy.basicfunctions;

import com.google.template.soy.data.SoyValue;
import com.google.template.soy.plugin.java.restricted.JavaPluginContext;
import com.google.template.soy.plugin.java.restricted.JavaValue;
import com.google.template.soy.plugin.java.restricted.JavaValueFactory;
Expand All @@ -43,7 +42,7 @@
// type.
@Signature(
returnType = "?",
parameterTypes = {"?", "?"},
parameterTypes = {"list<?>"},
isVarArgs = true))
@SoyPureFunction
public final class MaxFunction
Expand All @@ -52,25 +51,24 @@ public final class MaxFunction
@Override
public JavaScriptValue applyForJavaScriptSource(
JavaScriptValueFactory factory, List<JavaScriptValue> args, JavaScriptPluginContext context) {
return factory.global("Math").invokeMethod("max", args.get(0), args.get(1));
return factory.global("Math").invokeMethod("max", args);
}

@Override
public PythonValue applyForPythonSource(
PythonValueFactory factory, List<PythonValue> args, PythonPluginContext context) {
return factory.global("max").call(args.get(0), args.get(1));
return factory.global("max").call(args);
}

// lazy singleton pattern, allows other backends to avoid the work.
private static final class Methods {
private static final Method MAX_FN =
JavaValueFactory.createMethod(
BasicFunctionsRuntime.class, "max", SoyValue.class, SoyValue.class);
JavaValueFactory.createMethod(BasicFunctionsRuntime.class, "max", List.class);
}

@Override
public JavaValue applyForJavaSource(
JavaValueFactory factory, List<JavaValue> args, JavaPluginContext context) {
return factory.callStaticMethod(Methods.MAX_FN, args.get(0), args.get(1));
return factory.callStaticMethod(Methods.MAX_FN, args.toArray(new JavaValue[0]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public final class CallableExprBuilder {
private boolean isNullSafe;
private ExprNode target;
private ExprNode functionExpr;
private boolean isVarArgs;

public static CallableExprBuilder builder() {
return new CallableExprBuilder();
Expand All @@ -56,6 +57,7 @@ public static CallableExprBuilder builder(FunctionNode from) {
if (!from.hasStaticName()) {
builder.setFunctionExpr(from.getNameExpr());
}
builder.setIsVarArgs(from.isVarArgs());
return builder;
}

Expand Down Expand Up @@ -124,6 +126,12 @@ public CallableExprBuilder setTarget(ExprNode target) {
return this;
}

@CanIgnoreReturnValue
public CallableExprBuilder setIsVarArgs(boolean isVarArgs) {
this.isVarArgs = isVarArgs;
return this;
}

private ParamsStyle buildParamsStyle() {
if (paramValues.isEmpty()) {
Preconditions.checkState(paramNames == null || paramNames.isEmpty());
Expand Down Expand Up @@ -165,6 +173,7 @@ public FunctionNode buildFunction() {
paramNames != null ? ImmutableList.copyOf(paramNames) : ImmutableList.of(),
commaLocations != null ? ImmutableList.copyOf(commaLocations) : null);

node.setIsVarArgs(isVarArgs);
node.addChildren(paramValues);
return node;
}
Expand Down
3 changes: 3 additions & 0 deletions java/src/com/google/template/soy/exprtree/ExprNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ default Identifier getParamName(int i) {
return getParamNames().get(i);
}

/** Whether the call node has a variable arguments parameter. */
boolean isVarArgs();

/** How parameters are passed to the call. */
enum ParamsStyle {
NONE,
Expand Down
19 changes: 17 additions & 2 deletions java/src/com/google/template/soy/exprtree/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ private static final class FunctionState {
@Nullable private FunctionRef function;
@Nullable private ImmutableList<SoyType> allowedParamTypes;
private boolean allowedToInvokeAsFunction = false;
private boolean isVarArgs = false;
}

public static FunctionNode newPositional(
Expand Down Expand Up @@ -251,6 +252,12 @@ public boolean isResolved() {
return state.function != null;
}

/** Returns false if ResolveExpressionTypesPass has not run yet. */
@Override
public boolean isVarArgs() {
return state.isVarArgs;
}

public boolean allowedToInvokeAsFunction() {
return this.state.allowedToInvokeAsFunction;
}
Expand All @@ -259,6 +266,10 @@ public void setAllowedToInvokeAsFunction(boolean cond) {
this.state.allowedToInvokeAsFunction = cond;
}

public void setIsVarArgs(boolean isVarArgs) {
this.state.isVarArgs = isVarArgs;
}

public Object getSoyFunction() {
checkState(
this.state.function != null,
Expand All @@ -283,8 +294,9 @@ public void setSoyFunction(Object soyFunction) {
public void setAllowedParamTypes(List<SoyType> allowedParamTypes) {
checkState(paramsStyle == ParamsStyle.POSITIONAL || numParams() == 0);
checkState(
allowedParamTypes.size() == numParams(),
"allowedParamTypes.size (%s) != numParams (%s)",
state.isVarArgs || allowedParamTypes.size() == numParams(),
"function does not accept variable arguments and allowedParamTypes.size (%s) != numParams"
+ " (%s)",
allowedParamTypes.size(),
numParams());
this.state.allowedParamTypes = ImmutableList.copyOf(allowedParamTypes);
Expand Down Expand Up @@ -333,6 +345,9 @@ public String toSourceString() {
}
sourceSb.append(paramNames.get(i)).append(": ");
sourceSb.append(params.get(i).toSourceString());
if (i == params.size() - 1 && isVarArgs()) {
sourceSb.append("...");
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions java/src/com/google/template/soy/exprtree/MethodCallNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,10 @@ public Optional<ImmutableList<Point>> getCommaLocations() {
public Identifier getIdentifier() {
return methodName;
}

// TODO(b/431281119): Implement this method.
@Override
public boolean isVarArgs() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private static Extern asExtern(

FunctionType functionType =
FunctionType.of(
argTypes.stream().map(t -> Parameter.of("unused", t)).collect(toImmutableList()),
argTypes.stream().map(t -> Parameter.of("unused", t, false)).collect(toImmutableList()),
returnType);
JavaImpl java =
new JavaImpl() {
Expand Down
30 changes: 29 additions & 1 deletion java/src/com/google/template/soy/jbcsrc/JbcSrcValueFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import com.google.template.soy.types.SoyTypeRegistry;
import com.google.template.soy.types.UnknownType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -136,7 +137,7 @@ SoyExpression computeForJavaSource(List<SoyExpression> args) {
ErrorReporter.Checkpoint checkpoint = errorReporter.checkpoint();
checkState(fnNode.getParamTypes() != null, "allowed param types must be set");
checkState(
fnNode.getParamTypes().size() == args.size(),
fnNode.isVarArgs() || fnNode.getParamTypes().size() == args.size(),
"wrong # of allowed param types (%s), expected %s",
fnNode.getParamTypes(),
args.size());
Expand All @@ -151,6 +152,9 @@ SoyExpression computeForJavaSource(List<SoyExpression> args) {
return SoyExpression.SOY_NULL;
}
SoyJavaSourceFunction javaSrcFn = fnNode.getSourceFunction();
if (fnNode.isVarArgs()) {
args = handleVarArgs(args, fnNode);
}
return toSoyExpression(
(JbcSrcJavaValue)
javaSrcFn.applyForJavaSource(
Expand Down Expand Up @@ -584,4 +588,28 @@ private Expression nullGuard(Expression delegate) {
}
return JAVA_NULL_TO_SOY_NULL.invoke(delegate);
}

public static List<SoyExpression> handleVarArgs(
List<SoyExpression> args, JavaPluginExecContext fnNode) {
List<SoyExpression> newArgs = new ArrayList<>();
int paramCount = fnNode.getParamTypes().size();
int varArgsIndex = paramCount - 1;
// Add all of the params that aren't varargs.
if (varArgsIndex > 0) {
newArgs.addAll(args.subList(0, varArgsIndex));
}
if (args.size() >= paramCount) {
newArgs.add(
SoyExpression.forList(
ListType.of(fnNode.getParamTypes().get(varArgsIndex)),
SoyExpression.asBoxedValueProviderList(args.subList(varArgsIndex, args.size()))));
} else {
// No var args parameter found.
newArgs.add(
SoyExpression.forList(
ListType.of(fnNode.getParamTypes().get(varArgsIndex)),
SoyExpression.asBoxedValueProviderList(ImmutableList.of())));
}
return newArgs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ final class ResolveExpressionTypesPass extends AbstractTopologicallyOrderedPass
SoyErrorKind.of("Cannot set a variable of type ''{0}'' to a value of type ''{1}''.");
private static final SoyErrorKind GENERIC_PARAM_NOT_ASSIGNABLE =
SoyErrorKind.of("Argument of type ''{0}'' is not assignable to type ''{1}''.");
private static final SoyErrorKind VAR_ARGS_NOT_LIST =
SoyErrorKind.of("Var args parameter found and not transformed to a list.");

private final ErrorReporter errorReporter;

Expand Down Expand Up @@ -1800,7 +1802,7 @@ private List<SoyType> getParamTypes(SoyMethod method, SoyType baseType) {
arg =
FunctionType.of(
arg.getParameters().stream()
.map(p -> FunctionType.Parameter.of(p.getName(), itemType))
.map(p -> FunctionType.Parameter.of(p.getName(), itemType, p.isVarArgs()))
.collect(toImmutableList()),
arg.getReturnType());
return ImmutableList.of(arg);
Expand Down Expand Up @@ -2321,12 +2323,14 @@ protected void visitFunctionNode(FunctionNode node) {
* For soy functions with type annotation, perform the strict type checking and set the return
* type.
*/
// TODO(b/431043013): Add matching for var args here.
private void visitSoyFunctionWithSignature(
SoyFunctionSignature fnSignature, String className, FunctionNode node) {
ResolvedSignature matchedSignature = null;
// Found the matched signature for the current function call.
boolean isVarArgsSig = false;
for (Signature signature : fnSignature.value()) {
if (signature.parameterTypes().length == node.numParams()) {
if (signature.parameterTypes().length == node.numParams() && !isVarArgs(signature)) {
matchedSignature = getOrCreateFunctionSignature(signature, className, errorReporter);
if (!signature.deprecatedWarning().isEmpty()) {
errorReporter.warn(
Expand All @@ -2336,6 +2340,9 @@ private void visitSoyFunctionWithSignature(
signature.deprecatedWarning());
}
break;
} else if (isVarArgs(signature)) {
matchedSignature = getOrCreateFunctionSignature(signature, className, errorReporter);
isVarArgsSig = true;
}
}
if (matchedSignature == null) {
Expand All @@ -2346,15 +2353,36 @@ private void visitSoyFunctionWithSignature(
errorReporter.report(node.getFunctionNameLocation(), INCORRECT_ARG_STYLE);
return;
}
int totalParams = matchedSignature.parameterTypes().size();
int fixedParams = isVarArgsSig ? totalParams - 1 : totalParams;
for (int i = 0; i < node.numParams(); ++i) {
SoyType paramType = matchedSignature.parameterTypes().get(i);
maybeCoerceType(node.getParam(i), paramType);
checkArgType(node.getParam(i), paramType, node);
if (i < fixedParams) {
SoyType paramType = matchedSignature.parameterTypes().get(i);
maybeCoerceType(node.getParam(i), paramType);
checkArgType(node.getParam(i), paramType, node);
} else if (isVarArgsSig) {
if (matchedSignature.parameterTypes().get(totalParams - 1) instanceof ListType) {
ListType varArgsType =
(ListType) matchedSignature.parameterTypes().get(totalParams - 1);
SoyType paramType = varArgsType.getElementType();
maybeCoerceType(node.getParam(fixedParams), paramType);
checkArgType(node.getParam(fixedParams), paramType, node);
} else {
errorReporter.report(node.getSourceLocation(), VAR_ARGS_NOT_LIST);
}
}
}
node.setIsVarArgs(isVarArgsSig);
node.setAllowedParamTypes(matchedSignature.parameterTypes());
node.setType(matchedSignature.returnType());
}

private boolean isVarArgs(Signature signature) {
return signature.parameterTypes().length > 0
&& signature.isVarArgs()
&& signature.parameterTypes()[signature.parameterTypes().length - 1].startsWith("list");
}

private void visitKeysFunction(FunctionNode node) {
ListType listType;
SoyType argType = node.getParam(0).getType();
Expand Down Expand Up @@ -3242,7 +3270,6 @@ public ImmutableList<SoySourceFunctionMethod> load(String methodName) {
Arrays.stream(signature.parameterTypes())
.map(s -> parseType(s, fakeFunctionPath))
.collect(toImmutableList());

return new SoySourceFunctionMethod(
function,
baseType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,8 @@ public SoyType getReturnType() {
public Node getNode() {
return node;
}

public boolean isVarArgs() {
return node instanceof ExprNode.CallableExpr callableExpr && callableExpr.isVarArgs();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,7 @@ SoyValue visitExtern(

FunctionType functionType =
FunctionType.of(
argTypes.stream().map(t -> Parameter.of("unused", t)).collect(toImmutableList()),
argTypes.stream().map(t -> Parameter.of("unused", t, false)).collect(toImmutableList()),
returnType);
TofuValueFactory factory =
new TofuValueFactory(
Expand All @@ -1086,7 +1086,9 @@ SoyValue visitExtern(
pluginInstances,
functionType,
false,
new FunctionAdapter(this));
new FunctionAdapter(this),
functionType.getParameters().stream().map(p -> p.getType()).collect(toImmutableList()),
functionType.isVarArgs());
TofuJavaValue tofu =
Modifier.isStatic(method.getModifiers())
? factory.callStaticMethod(method, returnType, javaValues)
Expand Down Expand Up @@ -1138,7 +1140,11 @@ TofuJavaValue visitExtern(
pluginInstances,
externNode.getType(),
produceRawTofuValues,
new FunctionAdapter(this));
new FunctionAdapter(this),
externNode.getParamVars().stream()
.map(p -> p.getTypeNode().getResolvedType())
.collect(toImmutableList()),
externNode.getType().isVarArgs());
return java.isStatic()
? factory.callStaticMethod(method, resultType, javaValues)
: factory.callInstanceMethod(method, resultType, javaValues);
Expand Down
Loading