Skip to content

Commit

Permalink
One number type to rule them all.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 726199896
  • Loading branch information
Jesse-Good authored and copybara-github committed Feb 13, 2025
1 parent bed9f76 commit c2a78d9
Show file tree
Hide file tree
Showing 117 changed files with 1,055 additions and 1,855 deletions.
30 changes: 11 additions & 19 deletions documentation/reference/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,33 +62,25 @@ SoySauce | `boolean`, `com.google.template.soy.data.restricted.BooleanData`
Tofu | `com.google.template.soy.data.restricted.BooleanData`
Python | `bool`

### `int` {#int}
### `number` {#number}

An integer. Due to limitations of JavaScript this type is only guaranteed to
have 52 bit of precision.
A number. Due to limitations of JavaScript this type is only guaranteed to have
52 bit of precision.

Backend | type in host language
---------- | ----------------------------------------------------------
---------- | --------------------------------------------------------------
JavaScript | `number`
SoySauce | `long`, `com.google.template.soy.data.restricted.LongData`
Tofu | `com.google.template.soy.data.restricted.LongData`
Python | `long`
SoySauce | `double`, `com.google.template.soy.data.restricted.NumberData`
Tofu | `com.google.template.soy.data.restricted.NumberData`
Python | `long` or `float`

### `float` {#float}

A floating point number with 64 bits of precision.
### `int` {#int}

Backend | type in host language
---------- | ---------------------
JavaScript | `number`
SoySauce | `double`, `FloatData`
Tofu | `FloatData`
Python | `float`
A deprecated alias for [`number`](#number).

### `number` {#number}
### `float` {#float}

An alias for `int|float`. (Technically a [composite type](#union), not a
primitive.)
A deprecated alias for [`number`](#number).

### `gbigint` {#gbigint}

Expand Down
19 changes: 19 additions & 0 deletions java/src/com/google/template/soy/base/internal/BaseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,23 @@ public static void trimStackTraceTo(Throwable t, Class<?> cls) {
}
}
}

/** Returns Soy's idea of a double as a string. */
public static String formatDouble(double value) {
// This approximately consistent with Javascript for important cases.
// Reference: http://www.ecma-international.org/ecma-262/5.1/#sec-9.8.1
if (value % 1 == 0 && Math.abs(value) < Long.MAX_VALUE) {
// The value is non-fractional and within the magnitude of a long, so print as an integer
// instead of scientific notation. Note that Javascript uses 1.0e19 as the cutoff, but
// Long.MAX_VALUE is not that far off (9.2e18), and it is both easy and efficient to coerce
// to a long.
return String.valueOf((long) value);
} else {
// Note: This differs from JS in how it rendered values that have a zero fractional component
// and in how it renders the value -0.0.
// JavaScript specifies that the string form of -0 is signless, and that the string form of
// fractionless numeric values has no decimal point.
return Double.toString(value).replace('E', 'e');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public SoyExpression applyForJbcSrc(
UnionType.of(StringType.getInstance(), HtmlType.getInstance()),
JbcSrcMethods.INSERT_WORD_BREAKS.invoke(
value.box(),
BytecodeUtils.numericConversion(args.get(0).unboxAsLong(), Type.INT_TYPE)));
BytecodeUtils.numericConversion(args.get(0).unboxAsDouble(), Type.INT_TYPE)));
}

@Override
Expand All @@ -113,7 +113,7 @@ public AppendableAndOptions applyForJbcSrcStreaming(
return AppendableAndOptions.create(
JbcSrcMethods.INSERT_WORD_BREAKS_STREAMING.invoke(
delegateAppendable,
BytecodeUtils.numericConversion(args.get(0).unboxAsLong(), Type.INT_TYPE)));
BytecodeUtils.numericConversion(args.get(0).unboxAsDouble(), Type.INT_TYPE)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public SoyExpression applyForJbcSrc(
return SoyExpression.forString(
JbcSrcMethods.TRUNCATE.invoke(
value.coerceToString(),
BytecodeUtils.numericConversion(args.get(0).unboxAsLong(), Type.INT_TYPE),
BytecodeUtils.numericConversion(args.get(0).unboxAsDouble(), Type.INT_TYPE),
args.size() > 1 ? args.get(1).unboxAsBoolean() : BytecodeUtils.constant(true)));
}

Expand All @@ -117,7 +117,7 @@ public AppendableAndOptions applyForJbcSrcStreaming(
return AppendableAndOptions.createCloseable(
JbcSrcMethods.TRUNCATE_STREAMING.invoke(
delegateAppendable,
BytecodeUtils.numericConversion(args.get(0).unboxAsLong(), Type.INT_TYPE),
BytecodeUtils.numericConversion(args.get(0).unboxAsDouble(), Type.INT_TYPE),
args.size() > 1 ? args.get(1).unboxAsBoolean() : BytecodeUtils.constant(true)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@
import com.google.template.soy.data.internal.RuntimeMapTypeTracker;
import com.google.template.soy.data.internal.SoyMapImpl;
import com.google.template.soy.data.internal.SoyRecordImpl;
import com.google.template.soy.data.restricted.FloatData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NumberData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.shared.internal.Sanitizers;
Expand Down Expand Up @@ -74,11 +72,7 @@ public static boolean veHasSameId(SoyVisualElement ve1, SoyVisualElement ve2) {
* to the argument.
*/
public static long ceil(SoyValue arg) {
if (arg instanceof IntegerData) {
return arg.longValue();
} else {
return (long) Math.ceil(arg.floatValue());
}
return (long) Math.ceil(arg.floatValue());
}

/** Concatenates its arguments. */
Expand Down Expand Up @@ -218,8 +212,8 @@ public static ImmutableList<? extends SoyValueProvider> listFlat(

@Nonnull
public static ImmutableList<? extends SoyValueProvider> listFlat(
List<? extends SoyValueProvider> list, IntegerData data) {
return listFlatImpl(list, (int) data.getValue());
List<? extends SoyValueProvider> list, NumberData data) {
return listFlatImpl(list, data.integerValue());
}

@Nonnull
Expand Down Expand Up @@ -272,11 +266,7 @@ public static ImmutableList<SoyValueProvider> stringListSort(
* the argument.
*/
public static long floor(SoyValue arg) {
if (arg instanceof IntegerData) {
return arg.longValue();
} else {
return (long) Math.floor(arg.floatValue());
}
return (long) Math.floor(arg.floatValue());
}

/**
Expand Down Expand Up @@ -327,36 +317,28 @@ 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.numberValue(), arg1.numberValue()));
}
return NumberData.forValue(Math.max(arg0.floatValue(), arg1.floatValue()));
}

/** Returns the numeric minimum of the two arguments. */
public static NumberData min(SoyValue arg0, SoyValue arg1) {
if (arg0 instanceof IntegerData && arg1 instanceof IntegerData) {
return IntegerData.forValue(Math.min(arg0.longValue(), arg1.longValue()));
} else {
return FloatData.forValue(Math.min(arg0.numberValue(), arg1.numberValue()));
}
return NumberData.forValue(Math.min(arg0.floatValue(), arg1.floatValue()));
}

@Nullable
public static FloatData parseFloat(String str) {
public static NumberData parseFloat(String str) {
Double d = Doubles.tryParse(str);
return (d == null || d.isNaN()) ? null : FloatData.forValue(d);
return (d == null || d.isNaN()) ? null : NumberData.forValue(d);
}

@Nullable
public static IntegerData parseInt(String str, SoyValue radixVal) {
public static NumberData parseInt(String str, SoyValue radixVal) {
int radix = SoyValue.isNullish(radixVal) ? 10 : (int) radixVal.numberValue();
if (radix < 2 || radix > 36) {
return null;
}
Long l = Longs.tryParse(str, radix);
return (l == null) ? null : IntegerData.forValue(l);
return (l == null) ? null : NumberData.forValue(l);
}

/** Returns a random integer between {@code 0} and the provided argument. */
Expand All @@ -373,29 +355,25 @@ public static NumberData round(SoyValue value, int numDigitsAfterPoint) {
// NOTE: for more accurate rounding, this should really be using BigDecimal which can do correct
// decimal arithmetic. However, for compatibility with js, that probably isn't an option.
if (numDigitsAfterPoint == 0) {
return IntegerData.forValue(round(value));
return NumberData.forValue(round(value));
} else if (numDigitsAfterPoint > 0) {
double valueDouble = value.numberValue();
double shift = Math.pow(10, numDigitsAfterPoint);
return FloatData.forValue(Math.round(valueDouble * shift) / shift);
return NumberData.forValue(Math.round(valueDouble * shift) / shift);
} else {
double valueDouble = value.numberValue();
double shift = Math.pow(10, -numDigitsAfterPoint);
return IntegerData.forValue((int) (Math.round(valueDouble / shift) * shift));
return NumberData.forValue((int) (Math.round(valueDouble / shift) * shift));
}
}

/** Rounds the given value to the closest integer. */
public static long round(SoyValue value) {
if (value instanceof IntegerData) {
return value.longValue();
} else {
return Math.round(value.numberValue());
}
return Math.round(value.floatValue());
}

@Nonnull
public static List<IntegerData> range(int start, int end, int step) {
public static List<NumberData> range(int start, int end, int step) {
if (step == 0) {
throw new IllegalArgumentException(String.format("step must be non-zero: %d", step));
}
Expand All @@ -409,8 +387,8 @@ public static List<IntegerData> range(int start, int end, int step) {

return new AbstractList<>() {
@Override
public IntegerData get(int index) {
return IntegerData.forValue(start + step * index);
public NumberData get(int index) {
return NumberData.forValue(start + step * index);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.google.template.soy.basicfunctions;

import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NumberData;
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 Down Expand Up @@ -78,7 +78,7 @@ private static final class Methods {
JavaValueFactory.createMethod(BasicFunctionsRuntime.class, "listFlat", List.class);
static final Method LIST_FLAT_ARG1_FN =
JavaValueFactory.createMethod(
BasicFunctionsRuntime.class, "listFlat", List.class, IntegerData.class);
BasicFunctionsRuntime.class, "listFlat", List.class, NumberData.class);
}

@Override
Expand Down
11 changes: 5 additions & 6 deletions java/src/com/google/template/soy/data/ProtoFieldInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor.Syntax;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.Message;
import com.google.protobuf.ProtocolMessageEnum;
Expand All @@ -43,7 +42,7 @@
import com.google.template.soy.data.restricted.BooleanData;
import com.google.template.soy.data.restricted.FloatData;
import com.google.template.soy.data.restricted.GbigintData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NumberData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.internal.proto.FieldVisitor;
import com.google.template.soy.internal.proto.Int64ConversionMode;
Expand Down Expand Up @@ -317,7 +316,7 @@ public Object protoFromSoy(SoyValue field) {
new ProtoFieldInterpreter() {
@Override
public SoyValue soyFromProto(Object field) {
return IntegerData.forValue(((Number) field).longValue());
return NumberData.forValue(((Number) field).longValue());
}

@Override
Expand All @@ -331,7 +330,7 @@ public Object protoFromSoy(SoyValue field) {
new ProtoFieldInterpreter() {
@Override
public SoyValue soyFromProto(Object field) {
return IntegerData.forValue(UnsignedInts.toLong(((Number) field).intValue()));
return NumberData.forValue(UnsignedInts.toLong(((Number) field).intValue()));
}

@Override
Expand All @@ -345,7 +344,7 @@ public Object protoFromSoy(SoyValue field) {
new ProtoFieldInterpreter() {
@Override
public SoyValue soyFromProto(Object field) {
return IntegerData.forValue((Long) field);
return NumberData.forValue((Long) field);
}

@Override
Expand Down Expand Up @@ -567,7 +566,7 @@ public SoyValue soyFromProto(Object field) {
// ProtocolMessageEnum otherwise. Who knows why.
value = ((EnumValueDescriptor) field).getNumber();
}
return IntegerData.forValue(value);
return NumberData.forValue(value);
}

@Override
Expand Down
10 changes: 0 additions & 10 deletions java/src/com/google/template/soy/data/SoyValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,6 @@ public SoyValue checkNullishType(Class<? extends SoyValue> type) {
return type.cast(this);
}

/** A runtime type check for this boxed Soy value. */
public SoyValue checkNullishInt() {
throw new ClassCastException(classCastErrorMessage(this, "int"));
}

/** A runtime type check for this boxed Soy value. */
public SoyValue checkNullishFloat() {
throw new ClassCastException(classCastErrorMessage(this, "float"));
}

/** A runtime type check for this boxed Soy value. */
public SoyValue checkNullishNumber() {
throw new ClassCastException(classCastErrorMessage(this, "number"));
Expand Down
4 changes: 2 additions & 2 deletions java/src/com/google/template/soy/data/SoyValueConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ private SoyValueConverter() {
cheapConverterMap.put(String.class, StringData::forValue);
cheapConverterMap.put(Boolean.class, BooleanData::forValue);
cheapConverterMap.put(Integer.class, input -> IntegerData.forValue(input.longValue()));
cheapConverterMap.put(Long.class, IntegerData::forValue);
cheapConverterMap.put(Long.class, input -> IntegerData.forValue(input.longValue()));
cheapConverterMap.put(BigInteger.class, GbigintData::forValue);

cheapConverterMap.put(Float.class, input -> FloatData.forValue(input.doubleValue()));
cheapConverterMap.put(Double.class, FloatData::forValue);
cheapConverterMap.put(Double.class, input -> FloatData.forValue(input.doubleValue()));
cheapConverterMap.put(Future.class, (f) -> new SoyFutureValueProvider(f, this::convert));
// Proto enum that was obtained via reflection (e.g. from SoyProtoValue)
cheapConverterMap.put(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.template.soy.data.restricted.GbigintData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NullData;
import com.google.template.soy.data.restricted.NumberData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.data.restricted.UndefinedData;
import java.util.LinkedHashMap;
Expand All @@ -46,6 +47,7 @@ private SoyValueUnconverter() {}
CONVERTERS.put(BooleanData.class, BooleanData::getValue);
CONVERTERS.put(IntegerData.class, IntegerData::getValue);
CONVERTERS.put(FloatData.class, FloatData::getValue);
CONVERTERS.put(NumberData.class, NumberData::floatValue);
CONVERTERS.put(StringData.class, StringData::getValue);
CONVERTERS.put(GbigintData.class, GbigintData::getValue);
CONVERTERS.put(
Expand Down
3 changes: 1 addition & 2 deletions java/src/com/google/template/soy/data/internal/DictImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,7 @@ private String getStringKey(SoyValue key) {
return key.stringValue();
} catch (SoyDataException e) {
throw new SoyDataException(
"SoyDict accessed with non-string key (got key type " + key.getClass().getName() + ").",
e);
"SoyDict accessed with non-string key (got key type " + key.getSoyTypeName() + ").", e);
}
}

Expand Down
Loading

0 comments on commit c2a78d9

Please sign in to comment.