Skip to content

Commit

Permalink
Add NumberNode abstract superclass for FloatNode and IntNode, in prep…
Browse files Browse the repository at this point in the history
…aration for replacing those two with NumberNode.

PiperOrigin-RevId: 726647971
  • Loading branch information
Jesse-Good authored and copybara-github committed Feb 14, 2025
1 parent d8a2d03 commit 404f766
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2025 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.template.soy.base.internal;

/** Numeric coercions compatible with JavaScript number. */
public final class NumericCoercions {

private static final long MAX_SAFE_INTEGER = (1L << 53) - 1; // 2^53 - 1
private static final long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;

private NumericCoercions() {}

public static double safeDouble(long l) {
if (l > MAX_SAFE_INTEGER || l < MIN_SAFE_INTEGER) {
throw new IllegalArgumentException(String.valueOf(l));
}
return (double) l;
}

public static long safeLong(double d) {
if (d > MAX_SAFE_INTEGER || d < MIN_SAFE_INTEGER) {
throw new IllegalArgumentException(String.valueOf(d));
}
return (long) d;
}

public static int safeInt(long l) {
if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) {
throw new IllegalArgumentException(String.valueOf(l));
}
return (int) l;
}

public static int safeInt(double d) {
return safeInt(safeLong(d));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

package com.google.template.soy.data.restricted;

import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.Immutable;
import com.google.template.soy.base.internal.NumericCoercions;
import com.google.template.soy.data.SoyValue;
import javax.annotation.Nonnull;

Expand Down Expand Up @@ -118,11 +118,7 @@ public long getValue() {

@Override
public int integerValue() {
Preconditions.checkState(
value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE,
"Casting long to integer results in overflow: %s",
value);
return (int) value;
return NumericCoercions.safeInt(value);
}

@Override
Expand Down
18 changes: 17 additions & 1 deletion java/src/com/google/template/soy/exprtree/FloatNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
package com.google.template.soy.exprtree;

import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.NumericCoercions;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.types.FloatType;

/** Node representing a float value. */
public final class FloatNode extends AbstractPrimitiveNode {
public final class FloatNode extends NumberNode {

/** The float value */
private final double value;
Expand Down Expand Up @@ -69,4 +70,19 @@ public String toSourceString() {
public FloatNode copy(CopyState copyState) {
return new FloatNode(this, copyState);
}

@Override
public double doubleValue() {
return value;
}

@Override
public long longValue() {
return NumericCoercions.safeLong(value);
}

@Override
public int intValue() {
return NumericCoercions.safeInt(value);
}
}
18 changes: 17 additions & 1 deletion java/src/com/google/template/soy/exprtree/IntegerNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
package com.google.template.soy.exprtree;

import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.NumericCoercions;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.types.IntType;

/**
* Node representing a Soy integer value. Note that Soy supports up to JavaScript
* +-Number.MAX_SAFE_INTEGER at the least; Java and Python backends support full 64 bit longs.
*/
public final class IntegerNode extends AbstractPrimitiveNode {
public final class IntegerNode extends NumberNode {

// JavaScript Number.MAX_SAFE_INTEGER:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
Expand Down Expand Up @@ -87,4 +88,19 @@ public String toSourceString() {
public IntegerNode copy(CopyState copyState) {
return new IntegerNode(this, copyState);
}

@Override
public double doubleValue() {
return NumericCoercions.safeDouble(value);
}

@Override
public long longValue() {
return value;
}

@Override
public int intValue() {
return NumericCoercions.safeInt(value);
}
}
41 changes: 41 additions & 0 deletions java/src/com/google/template/soy/exprtree/NumberNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2025 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.template.soy.exprtree;

import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.basetree.CopyState;

/**
* Node representing a Soy integer value. Note that Soy supports up to JavaScript
* +-Number.MAX_SAFE_INTEGER at the least; Java and Python backends support full 64 bit longs.
*/
public abstract class NumberNode extends AbstractPrimitiveNode {

public NumberNode(SourceLocation sourceLocation) {
super(sourceLocation);
}

protected NumberNode(NumberNode orig, CopyState copyState) {
super(orig, copyState);
}

public abstract double doubleValue();

public abstract long longValue();

public abstract int intValue();
}
19 changes: 9 additions & 10 deletions java/src/com/google/template/soy/jbcsrc/ProtoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,11 @@
import com.google.template.soy.data.SanitizedContents;
import com.google.template.soy.data.SoyValue;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.FloatNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.IntegerNode;
import com.google.template.soy.exprtree.ListLiteralNode;
import com.google.template.soy.exprtree.MapLiteralNode;
import com.google.template.soy.exprtree.MethodCallNode;
import com.google.template.soy.exprtree.NumberNode;
import com.google.template.soy.exprtree.ProtoEnumValueNode;
import com.google.template.soy.exprtree.VarDefn;
import com.google.template.soy.exprtree.VarRefNode;
Expand Down Expand Up @@ -1034,9 +1033,9 @@ protected void doGen(CodeBuilder cb) {
}
};
}
if (field.getJavaType() == JavaType.INT && arg.getKind() == ExprNode.Kind.INTEGER_NODE) {
if (field.getJavaType() == JavaType.INT && arg instanceof NumberNode) {
// similar to the above, we can avoid a L2I instruction or a method call
long value = ((IntegerNode) arg).getValue();
long value = ((NumberNode) arg).longValue();
return new Statement() {
@Override
protected void doGen(CodeBuilder cb) {
Expand All @@ -1049,8 +1048,8 @@ protected void doGen(CodeBuilder cb) {
}
};
}
if (field.getJavaType() == JavaType.FLOAT && arg.getKind() == ExprNode.Kind.FLOAT_NODE) {
float value = (float) ((FloatNode) arg).getValue();
if (field.getJavaType() == JavaType.FLOAT && arg instanceof NumberNode) {
float value = (float) ((NumberNode) arg).doubleValue();
return new Statement() {
@Override
protected void doGen(CodeBuilder cb) {
Expand Down Expand Up @@ -1384,9 +1383,9 @@ protected void doGen(CodeBuilder cb) {
}
};
}
if (field.getJavaType() == JavaType.INT && arg.getKind() == ExprNode.Kind.INTEGER_NODE) {
if (field.getJavaType() == JavaType.INT && arg instanceof NumberNode) {
// similar to the above, we can avoid a L2I instruction or a method call
long value = ((IntegerNode) arg).getValue();
long value = ((NumberNode) arg).longValue();
int intValue = isUnsigned(field) ? UnsignedInts.saturatedCast(value) : (int) value;
Expression boxedInt = BytecodeUtils.boxJavaPrimitive(Type.INT_TYPE, constant(intValue));
return new Statement() {
Expand All @@ -1399,8 +1398,8 @@ protected void doGen(CodeBuilder cb) {
}
};
}
if (field.getJavaType() == JavaType.FLOAT && arg.getKind() == ExprNode.Kind.FLOAT_NODE) {
float value = (float) ((FloatNode) arg).getValue();
if (field.getJavaType() == JavaType.FLOAT && arg instanceof NumberNode) {
float value = (float) ((NumberNode) arg).doubleValue();
Expression boxedFloat = BytecodeUtils.boxJavaPrimitive(Type.FLOAT_TYPE, constant(value));
return new Statement() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.GlobalNode;
import com.google.template.soy.exprtree.IntegerNode;
import com.google.template.soy.exprtree.ListComprehensionNode;
import com.google.template.soy.exprtree.ListLiteralNode;
import com.google.template.soy.exprtree.MapLiteralFromListNode;
import com.google.template.soy.exprtree.MapLiteralNode;
import com.google.template.soy.exprtree.NullSafeAccessNode;
import com.google.template.soy.exprtree.NumberNode;
import com.google.template.soy.exprtree.OperatorNodes.AmpAmpOpNode;
import com.google.template.soy.exprtree.OperatorNodes.AssertNonNullOpNode;
import com.google.template.soy.exprtree.OperatorNodes.BarBarOpNode;
Expand Down Expand Up @@ -641,8 +641,8 @@ private static StaticAnalysisResult isListExpressionEmpty(ForNode node) {
private static StaticAnalysisResult isRangeExpressionEmpty(RangeArgs range) {
int start = 0;
if (range.start().isPresent()) {
if (range.start().get() instanceof IntegerNode) {
long startAsLong = ((IntegerNode) range.start().get()).getValue();
if (range.start().get() instanceof NumberNode) {
long startAsLong = ((NumberNode) range.start().get()).longValue();
if (startAsLong != (int) startAsLong) {
return StaticAnalysisResult.UNKNOWN;
}
Expand All @@ -654,8 +654,8 @@ private static StaticAnalysisResult isRangeExpressionEmpty(RangeArgs range) {
}

int limit;
if (range.limit() instanceof IntegerNode) {
long limitAsLong = ((IntegerNode) range.limit()).getValue();
if (range.limit() instanceof NumberNode) {
long limitAsLong = ((NumberNode) range.limit()).longValue();
if (limitAsLong != (int) limitAsLong) {
return StaticAnalysisResult.UNKNOWN;
}
Expand All @@ -666,8 +666,8 @@ private static StaticAnalysisResult isRangeExpressionEmpty(RangeArgs range) {

int step = 1;
if (range.increment().isPresent()) {
if (range.increment().get() instanceof IntegerNode) {
long stepAsLong = ((IntegerNode) range.increment().get()).getValue();
if (range.increment().get() instanceof NumberNode) {
long stepAsLong = ((NumberNode) range.increment().get()).longValue();
if (stepAsLong != (int) stepAsLong) {
return StaticAnalysisResult.UNKNOWN;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@
import com.google.template.soy.exprtree.FunctionNode.ExternRef;
import com.google.template.soy.exprtree.GlobalNode;
import com.google.template.soy.exprtree.GroupNode;
import com.google.template.soy.exprtree.IntegerNode;
import com.google.template.soy.exprtree.ItemAccessNode;
import com.google.template.soy.exprtree.ListComprehensionNode;
import com.google.template.soy.exprtree.ListLiteralNode;
Expand All @@ -105,6 +104,7 @@
import com.google.template.soy.exprtree.MethodCallNode;
import com.google.template.soy.exprtree.NullNode;
import com.google.template.soy.exprtree.NullSafeAccessNode;
import com.google.template.soy.exprtree.NumberNode;
import com.google.template.soy.exprtree.OperatorNodes.AmpAmpOpNode;
import com.google.template.soy.exprtree.OperatorNodes.AsOpNode;
import com.google.template.soy.exprtree.OperatorNodes.AssertNonNullOpNode;
Expand Down Expand Up @@ -939,8 +939,8 @@ protected void visitCallDelegateNode(CallDelegateNode node) {
if (!BaseUtils.isIdentifier(variantStr)) {
errorReporter.report(location, INVALID_VARIANT_EXPRESSION, variantStr);
}
} else if (variant.getRoot().getKind() == ExprNode.Kind.INTEGER_NODE) {
long variantInt = ((IntegerNode) variant.getRoot()).getValue();
} else if (variant.getRoot() instanceof NumberNode) {
long variantInt = ((NumberNode) variant.getRoot()).longValue();
if (variantInt < 0) {
errorReporter.report(location, INVALID_VARIANT_EXPRESSION, variant.toSourceString());
}
Expand Down Expand Up @@ -1640,8 +1640,8 @@ private void finishMethodCallNode(MethodCallNode node, boolean nullSafe) {
int maxDepth;
if (node.numParams() == 1) {
// This will only work for int literal in the source code.
if (node.getParam(0).getKind() == ExprNode.Kind.INTEGER_NODE) {
maxDepth = (int) ((IntegerNode) node.getParam(0)).getValue();
if (node.getParam(0) instanceof NumberNode) {
maxDepth = ((NumberNode) node.getParam(0)).intValue();
} else {
maxDepth = 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import com.google.template.soy.exprtree.ExprEquivalence;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.IntegerNode;
import com.google.template.soy.exprtree.NullNode;
import com.google.template.soy.exprtree.NumberNode;
import com.google.template.soy.exprtree.StringNode;
import com.google.template.soy.logging.LoggingConfigValidator;
import com.google.template.soy.logging.LoggingConfigValidator.VisualElement;
Expand Down Expand Up @@ -114,11 +114,11 @@ private void buildVeDefAndValidate(FunctionNode func, List<VisualElement> vedefs
}
String veName = ((StringNode) func.getParam(0)).getValue();

if (!(func.getParam(1) instanceof IntegerNode)) {
if (!(func.getParam(1) instanceof NumberNode)) {
errorReporter.report(func.getParam(1).getSourceLocation(), BAD_VE_DEF_ID);
return;
}
long id = ((IntegerNode) func.getParam(1)).getValue();
long id = ((NumberNode) func.getParam(1)).longValue();

Optional<String> dataProtoType;
if (func.numParams() < 3 || func.getParam(2) instanceof NullNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import com.google.template.soy.exprtree.MapLiteralNode;
import com.google.template.soy.exprtree.MethodCallNode;
import com.google.template.soy.exprtree.NullSafeAccessNode;
import com.google.template.soy.exprtree.NumberNode;
import com.google.template.soy.exprtree.OperatorNodes.AmpAmpOpNode;
import com.google.template.soy.exprtree.OperatorNodes.AsOpNode;
import com.google.template.soy.exprtree.OperatorNodes.BarBarOpNode;
Expand Down Expand Up @@ -266,9 +267,9 @@ private static ExprNode visitItemAccessNode(ItemAccessNode node, ExprNode baseEx
ExprNode keyExpr = node.getChild(1);
if (baseExpr instanceof ListLiteralNode
&& !((ListLiteralNode) baseExpr).containsSpreads()
&& keyExpr instanceof IntegerNode) {
&& keyExpr instanceof NumberNode) {
ListLiteralNode listLiteral = (ListLiteralNode) baseExpr;
long index = ((IntegerNode) keyExpr).getValue();
long index = ((NumberNode) keyExpr).longValue();
if (index >= 0 && index < listLiteral.numChildren()) {
return listLiteral.getChild((int) index);
} else {
Expand Down
Loading

0 comments on commit 404f766

Please sign in to comment.