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
32 changes: 30 additions & 2 deletions java/src/com/google/template/soy/data/SoyProtoValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.CaseFormat;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
Expand All @@ -27,8 +28,10 @@
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.UndefinedData;
import com.google.template.soy.internal.proto.Field;
import com.google.template.soy.internal.proto.Int64ConversionMode;
Expand Down Expand Up @@ -60,12 +63,16 @@ public final class SoyProtoValue extends SoyRecord {

private static final class ProtoClass {
final ImmutableMap<String, FieldWithInterpreter> fields;
final ImmutableMap<String, OneofDescriptor> oneofs;
final Message defaultInstance;
final String topLevelName;
final String fullName;
final String importPath;

ProtoClass(Message defaultInstance, ImmutableMap<String, FieldWithInterpreter> fields) {
ProtoClass(
Message defaultInstance,
ImmutableMap<String, FieldWithInterpreter> fields,
ImmutableMap<String, OneofDescriptor> oneofs) {
this.fullName = defaultInstance.getDescriptorForType().getFullName();
Descriptor d = defaultInstance.getDescriptorForType();
while (d.getContainingType() != null) {
Expand All @@ -75,6 +82,7 @@ private static final class ProtoClass {
this.importPath = defaultInstance.getDescriptorForType().getFile().getFullName();
this.defaultInstance = checkNotNull(defaultInstance);
this.fields = checkNotNull(fields);
this.oneofs = checkNotNull(oneofs);
}
}

Expand Down Expand Up @@ -137,9 +145,19 @@ public void assignField(Message.Builder builder, SoyValue value) {
@Override
public ProtoClass load(Descriptor descriptor) throws Exception {
Set<FieldDescriptor> extensions = new LinkedHashSet<>();
ImmutableMap.Builder<String, OneofDescriptor> oneofs = ImmutableMap.builder();
descriptor
.getOneofs()
.forEach(
o ->
oneofs.put(
CaseFormat.LOWER_UNDERSCORE.to(
CaseFormat.LOWER_CAMEL, o.getName()),
o));
return new ProtoClass(
getDefaultInstance(descriptor),
Field.getFieldsForType(descriptor, extensions, factory));
Field.getFieldsForType(descriptor, extensions, factory),
oneofs.build());
}
});

Expand Down Expand Up @@ -262,6 +280,16 @@ public boolean hasProtoField(String name) {
}
}

public SoyValue getProtoOneofCase(String name) {
OneofDescriptor oneof = clazz().oneofs.get(name);
if (oneof == null) {
throw new IllegalArgumentException(
"Proto " + proto.getClass().getName() + " does not have a oneof of name " + name);
}
FieldDescriptor setField = proto.getOneofFieldDescriptor(oneof);
return IntegerData.forValue(setField != null ? setField.getNumber() : 0);
}

public void setAccessLocationKey(Object location) {
this.locationKey = location;
}
Expand Down
14 changes: 10 additions & 4 deletions java/src/com/google/template/soy/exprtree/ProtoEnumValueNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
import com.google.template.soy.base.internal.Identifier;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.types.SoyProtoEnumType;
import com.google.template.soy.types.SoyType;
import javax.annotation.Nullable;

/** Node representing a proto enum value. */
public final class ProtoEnumValueNode extends AbstractPrimitiveNode {

private final Identifier id;
private final SoyProtoEnumType type;
private final SoyType type;
private final int enumNumber;

public ProtoEnumValueNode(Identifier id, SoyProtoEnumType type, int enumNumber) {
public ProtoEnumValueNode(Identifier id, SoyType type, int enumNumber) {
super(id.location());
this.id = id;
this.type = type;
Expand All @@ -52,7 +54,7 @@ public Kind getKind() {
}

@Override
public SoyProtoEnumType getType() {
public SoyType getType() {
return type;
}

Expand All @@ -65,8 +67,12 @@ public int getValueAsInt() {
return enumNumber;
}

@Nullable
public EnumValueDescriptor getEnumValueDescriptor() {
return type.getDescriptor().findValueByNumber(enumNumber);
if (type instanceof SoyProtoEnumType soyProtoEnumType) {
return soyProtoEnumType.getDescriptor().findValueByNumber(enumNumber);
}
return null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
import com.google.template.soy.logging.LoggingFunction;
import com.google.template.soy.shared.internal.BuiltinFunction;
import com.google.template.soy.soytree.defn.TemplateStateVar;
import com.google.template.soy.types.SoyProtoEnumType;
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
import com.google.template.soy.types.SoyType;
import com.google.template.soy.types.SoyType.Kind;
import com.google.template.soy.types.SoyTypes;
Expand Down Expand Up @@ -161,8 +163,18 @@ protected JsType jsTypeFor(SoyType type) {

@Override
protected Expression visitProtoEnumValueNode(ProtoEnumValueNode node) {
return GoogRequire.create(node.getType().getNameForBackend(SoyBackendKind.JS_SRC))
.googModuleGet()
.dotAccess(Ascii.toUpperCase(node.getEnumValueDescriptor().getName()));
String module;
String valueName;
if (node.getType() instanceof SoyProtoEnumType enumType) {
module = enumType.getNameForBackend(SoyBackendKind.JS_SRC);
valueName = node.getEnumValueDescriptor().getName();
} else if (node.getType() instanceof SoyProtoOneofCaseEnumType oneofCaseType) {
module = oneofCaseType.getNameForBackend(SoyBackendKind.JS_SRC);
valueName = oneofCaseType.getNameForValue((int) node.getValue());
} else {
throw new AssertionError("Unexpected type: " + node.getType());
}

return GoogRequire.create(module).googModuleGet().dotAccess(Ascii.toUpperCase(valueName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,12 @@ private SoyExpression visitMethodCall(SoyExpression baseExpr, MethodCallNode nod
ProtoUtils.SingularFieldAccessMode.DEFAULT_IF_UNSET,
varManager,
Int64ConversionMode.FOLLOW_JS_TYPE);
case GET_PROTO_ONEOF_CASE:
return ProtoUtils.accessOneofCase(
baseExpr,
BuiltinMethod.getProtoFieldNameFromMethodCall(node),
node.getType(),
varManager);
case GET_PROTO_FIELD:
return ProtoUtils.accessField(
baseExpr,
Expand Down
50 changes: 50 additions & 0 deletions java/src/com/google/template/soy/jbcsrc/ProtoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static com.google.template.soy.jbcsrc.restricted.BytecodeUtils.newLabel;
import static com.google.template.soy.jbcsrc.restricted.BytecodeUtils.numericConversion;

import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
Expand Down Expand Up @@ -257,6 +258,55 @@ static SoyExpression accessField(
}
}

static SoyExpression accessOneofCase(
SoyExpression baseExpr,
String oneofName,
SoyType fieldType,
LocalVariableManager varManager) {
SoyType type = baseExpr.soyType().getEffectiveType();
if (type instanceof SoyProtoType soyProtoType) {
return accessOneofCase(soyProtoType, baseExpr, oneofName);
} else {
return accessProtoUnionField(
baseExpr,
oneofName,
fieldType,
varManager,
expr -> {
SoyProtoType protoType = expr.soyType().asType(SoyProtoType.class);
return accessOneofCase(protoType, expr, oneofName);
});
}
}

private static SoyExpression accessOneofCase(
SoyProtoType protoType, SoyExpression baseExpr, String oneofName) {
OneofDescriptor descriptor =
protoType.getDescriptor().getOneofs().stream()
.filter(
o ->
CaseFormat.LOWER_UNDERSCORE
.to(CaseFormat.LOWER_CAMEL, o.getName())
.equals(oneofName))
.findFirst()
.get();
SoyRuntimeType unboxedRuntimeType = SoyRuntimeType.getUnboxedType(protoType).get();
Expression typedBaseExpr = baseExpr.unboxAsMessageUnchecked(unboxedRuntimeType.runtimeType());
MethodRef getCaseMethod = getOneOfCaseMethod(descriptor);
Expression invokeGetCase = typedBaseExpr.invoke(getCaseMethod);

MethodRef getNumberMethod =
MethodRef.createInstanceMethod(
TypeInfo.createClass(JavaQualifiedNames.getCaseEnumClassName(descriptor)),
new Method("getNumber", Type.INT_TYPE, MethodRef.NO_METHOD_ARGS),
MethodPureness.PURE);

Expression getNumber = invokeGetCase.invoke(getNumberMethod);
Expression longNumber = numericConversion(getNumber, Type.LONG_TYPE);

return SoyExpression.forInt(longNumber);
}

/**
* Returns a {@link SoyExpression} for accessing a field of a proto, limited to a single type of
* proto if the baseExpr can be a union of protos.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.template.soy.internal.proto.ProtoUtils;
import com.google.template.soy.jssrc.dsl.CodeChunk;
import com.google.template.soy.jssrc.dsl.Expression;
Expand Down Expand Up @@ -478,6 +479,7 @@ private enum Type {
GET("get", ""),
GET_OR_UNDEFINED("get", "OrUndefined"),
GET_READONLY("getReadonly", ""),
GET_ONEOF_CASE("get", "Case"),
HAS("has", "");

private final String prefix;
Expand Down Expand Up @@ -538,6 +540,12 @@ static ProtoCall hasField(String fieldName, FieldDescriptor desc) {
return accessor(fieldName, desc, Type.HAS);
}

static ProtoCall getOneofCase(String oneofName, OneofDescriptor desc) {
String getter = "get" + LOWER_CAMEL.to(UPPER_CAMEL, oneofName) + "Case";
return new AutoValue_NullSafeAccumulator_ProtoCall(
getter, /* getterArg= */ null, /* accessType= */ null, /* unpackFunction= */ null);
}

private static ProtoCall accessor(String fieldName, FieldDescriptor desc, Type type) {
String getter;
Expression arg;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import static com.google.template.soy.passes.ContentSecurityPolicyNonceInjectionPass.CSP_STYLE_NONCE_VARIABLE_NAME;

import com.google.common.base.Ascii;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
Expand All @@ -76,6 +77,7 @@
import com.google.common.collect.Iterables;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.SoyBackendKind;
import com.google.template.soy.base.internal.Identifier;
Expand Down Expand Up @@ -153,6 +155,8 @@
import com.google.template.soy.types.ListType;
import com.google.template.soy.types.MapType;
import com.google.template.soy.types.NumberType;
import com.google.template.soy.types.SoyProtoEnumType;
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
import com.google.template.soy.types.SoyProtoType;
import com.google.template.soy.types.SoyType;
import com.google.template.soy.types.SoyTypes;
Expand Down Expand Up @@ -344,10 +348,23 @@ protected Expression visitStringNode(StringNode node) {

@Override
protected Expression visitProtoEnumValueNode(ProtoEnumValueNode node) {
String module = node.getType().getNameForBackend(SoyBackendKind.JS_SRC);
String module;
String valueName;
if (node.getType() instanceof SoyProtoEnumType) {
SoyProtoEnumType enumType = (SoyProtoEnumType) node.getType();
module = enumType.getNameForBackend(SoyBackendKind.JS_SRC);
valueName = node.getEnumValueDescriptor().getName();
} else if (node.getType() instanceof SoyProtoOneofCaseEnumType) {
SoyProtoOneofCaseEnumType oneofCaseType = (SoyProtoOneofCaseEnumType) node.getType();
module = oneofCaseType.getNameForBackend(SoyBackendKind.JS_SRC);
valueName = oneofCaseType.getNameForValue((int) node.getValue());
} else {
throw new AssertionError("Unexpected type: " + node.getType());
}

return GoogRequire.create(module)
.reference()
.dotAccess(Id.create(Ascii.toUpperCase(node.getEnumValueDescriptor().getName())));
.dotAccess(Id.create(Ascii.toUpperCase(valueName)));
}

// -----------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -789,6 +806,27 @@ private NullSafeAccumulator genCodeForMethodCall(
ProtoCall.getReadonlyField(
fieldName, ((SoyProtoType) type).getFieldDescriptor(fieldName)));
return base.dotAccess(fieldAccess, nullSafe);
case GET_PROTO_ONEOF_CASE:
String oneofName = BuiltinMethod.getProtoFieldNameFromMethodCall(methodCallNode);
fieldAccess =
genCodeForMaybeUnion(
baseType,
oneofName,
sourceLocation,
type -> {
SoyProtoType protoType = (SoyProtoType) type;
OneofDescriptor oneof =
protoType.getDescriptor().getOneofs().stream()
.filter(
o ->
CaseFormat.LOWER_UNDERSCORE
.to(CaseFormat.LOWER_CAMEL, o.getName())
.equals(oneofName))
.findFirst()
.get();
return ProtoCall.getOneofCase(oneofName, oneof);
});
return base.dotAccess(fieldAccess, nullSafe);
// When adding new built-in methods it may be necessary to assert that the base expression
// is not null in order to prevent a method call on a null instance from ever succeeding.
case MAP_GET:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.base.internal.Identifier;
Expand Down Expand Up @@ -48,7 +49,9 @@
import com.google.template.soy.types.NamespaceType;
import com.google.template.soy.types.ProtoEnumImportType;
import com.google.template.soy.types.ProtoImportType;
import com.google.template.soy.types.ProtoOneofCaseImportType;
import com.google.template.soy.types.SoyProtoEnumType;
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
import com.google.template.soy.types.SoyType;
import com.google.template.soy.types.StringType;
import com.google.template.soy.types.TemplateImportType;
Expand Down Expand Up @@ -175,15 +178,28 @@ private ExprNode resolveField(
SoyType type = defn.type();

if (defn.getSymbolKind() == SymbolKind.PROTO_ENUM) {
EnumDescriptor enumDescriptor = ((ProtoEnumImportType) type).getDescriptor();
EnumValueDescriptor val = enumDescriptor.findValueByName(fieldName);
Identifier id = Identifier.create(refn.getName() + "." + fieldName, fullLocation);
SoyProtoEnumType soyType = typeRegistry.getOrCreateProtoEnumType(enumDescriptor);
if (val != null) {
return new ProtoEnumValueNode(id, soyType, val.getNumber());
} else {
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
return new ProtoEnumValueNode(id, soyType, 0);
if (type instanceof ProtoEnumImportType) {
EnumDescriptor enumDescriptor = ((ProtoEnumImportType) type).getDescriptor();
EnumValueDescriptor val = enumDescriptor.findValueByName(fieldName);
SoyProtoEnumType soyType = typeRegistry.getOrCreateProtoEnumType(enumDescriptor);
if (val != null) {
return new ProtoEnumValueNode(id, soyType, val.getNumber());
} else {
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
return new ProtoEnumValueNode(id, soyType, 0);
}
} else if (type instanceof ProtoOneofCaseImportType protoOneofCaseImportType) {
OneofDescriptor oneofDescriptor = protoOneofCaseImportType.getDescriptor();
SoyProtoOneofCaseEnumType soyType =
typeRegistry.getOrCreateProtoOneofCaseEnumType(oneofDescriptor);
Integer val = soyType.getValue(fieldName);
if (val != null) {
return new ProtoEnumValueNode(id, soyType, val);
} else {
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
return new ProtoEnumValueNode(id, soyType, 0);
}
}
}

Expand Down
Loading
Loading