Skip to content

Commit 5e66f8b

Browse files
Soy Authorscopybara-github
authored andcommitted
Support Proto OneOf Cases in Soy.
PiperOrigin-RevId: 909590253
1 parent 2ead6d8 commit 5e66f8b

16 files changed

Lines changed: 485 additions & 19 deletions

File tree

java/src/com/google/template/soy/data/SoyProtoValue.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

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

21+
import com.google.common.base.CaseFormat;
2122
import com.google.common.cache.CacheBuilder;
2223
import com.google.common.cache.CacheLoader;
2324
import com.google.common.cache.LoadingCache;
@@ -27,8 +28,10 @@
2728
import com.google.protobuf.Descriptors.Descriptor;
2829
import com.google.protobuf.Descriptors.FieldDescriptor;
2930
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
31+
import com.google.protobuf.Descriptors.OneofDescriptor;
3032
import com.google.protobuf.Message;
3133
import com.google.protobuf.TextFormat;
34+
import com.google.template.soy.data.restricted.IntegerData;
3235
import com.google.template.soy.data.restricted.UndefinedData;
3336
import com.google.template.soy.internal.proto.Field;
3437
import com.google.template.soy.internal.proto.Int64ConversionMode;
@@ -60,12 +63,16 @@ public final class SoyProtoValue extends SoyRecord {
6063

6164
private static final class ProtoClass {
6265
final ImmutableMap<String, FieldWithInterpreter> fields;
66+
final ImmutableMap<String, OneofDescriptor> oneofs;
6367
final Message defaultInstance;
6468
final String topLevelName;
6569
final String fullName;
6670
final String importPath;
6771

68-
ProtoClass(Message defaultInstance, ImmutableMap<String, FieldWithInterpreter> fields) {
72+
ProtoClass(
73+
Message defaultInstance,
74+
ImmutableMap<String, FieldWithInterpreter> fields,
75+
ImmutableMap<String, OneofDescriptor> oneofs) {
6976
this.fullName = defaultInstance.getDescriptorForType().getFullName();
7077
Descriptor d = defaultInstance.getDescriptorForType();
7178
while (d.getContainingType() != null) {
@@ -75,6 +82,7 @@ private static final class ProtoClass {
7582
this.importPath = defaultInstance.getDescriptorForType().getFile().getFullName();
7683
this.defaultInstance = checkNotNull(defaultInstance);
7784
this.fields = checkNotNull(fields);
85+
this.oneofs = checkNotNull(oneofs);
7886
}
7987
}
8088

@@ -137,9 +145,19 @@ public void assignField(Message.Builder builder, SoyValue value) {
137145
@Override
138146
public ProtoClass load(Descriptor descriptor) throws Exception {
139147
Set<FieldDescriptor> extensions = new LinkedHashSet<>();
148+
ImmutableMap.Builder<String, OneofDescriptor> oneofs = ImmutableMap.builder();
149+
descriptor
150+
.getOneofs()
151+
.forEach(
152+
o ->
153+
oneofs.put(
154+
CaseFormat.LOWER_UNDERSCORE.to(
155+
CaseFormat.LOWER_CAMEL, o.getName()),
156+
o));
140157
return new ProtoClass(
141158
getDefaultInstance(descriptor),
142-
Field.getFieldsForType(descriptor, extensions, factory));
159+
Field.getFieldsForType(descriptor, extensions, factory),
160+
oneofs.build());
143161
}
144162
});
145163

@@ -262,6 +280,16 @@ public boolean hasProtoField(String name) {
262280
}
263281
}
264282

283+
public SoyValue getProtoOneofCase(String name) {
284+
OneofDescriptor oneof = clazz().oneofs.get(name);
285+
if (oneof == null) {
286+
throw new IllegalArgumentException(
287+
"Proto " + proto.getClass().getName() + " does not have a oneof of name " + name);
288+
}
289+
FieldDescriptor setField = proto.getOneofFieldDescriptor(oneof);
290+
return IntegerData.forValue(setField != null ? setField.getNumber() : 0);
291+
}
292+
265293
public void setAccessLocationKey(Object location) {
266294
this.locationKey = location;
267295
}

java/src/com/google/template/soy/exprtree/ProtoEnumValueNode.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
import com.google.template.soy.base.internal.Identifier;
2121
import com.google.template.soy.basetree.CopyState;
2222
import com.google.template.soy.types.SoyProtoEnumType;
23+
import com.google.template.soy.types.SoyType;
24+
import javax.annotation.Nullable;
2325

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

2729
private final Identifier id;
28-
private final SoyProtoEnumType type;
30+
private final SoyType type;
2931
private final int enumNumber;
3032

31-
public ProtoEnumValueNode(Identifier id, SoyProtoEnumType type, int enumNumber) {
33+
public ProtoEnumValueNode(Identifier id, SoyType type, int enumNumber) {
3234
super(id.location());
3335
this.id = id;
3436
this.type = type;
@@ -52,7 +54,7 @@ public Kind getKind() {
5254
}
5355

5456
@Override
55-
public SoyProtoEnumType getType() {
57+
public SoyType getType() {
5658
return type;
5759
}
5860

@@ -65,8 +67,12 @@ public int getValueAsInt() {
6567
return enumNumber;
6668
}
6769

70+
@Nullable
6871
public EnumValueDescriptor getEnumValueDescriptor() {
69-
return type.getDescriptor().findValueByNumber(enumNumber);
72+
if (type instanceof SoyProtoEnumType soyProtoEnumType) {
73+
return soyProtoEnumType.getDescriptor().findValueByNumber(enumNumber);
74+
}
75+
return null;
7076
}
7177

7278
@Override

java/src/com/google/template/soy/idomsrc/IdomTranslateExprNodeVisitor.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import com.google.template.soy.logging.LoggingFunction;
5252
import com.google.template.soy.shared.internal.BuiltinFunction;
5353
import com.google.template.soy.soytree.defn.TemplateStateVar;
54+
import com.google.template.soy.types.SoyProtoEnumType;
55+
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
5456
import com.google.template.soy.types.SoyType;
5557
import com.google.template.soy.types.SoyType.Kind;
5658
import com.google.template.soy.types.SoyTypes;
@@ -161,8 +163,18 @@ protected JsType jsTypeFor(SoyType type) {
161163

162164
@Override
163165
protected Expression visitProtoEnumValueNode(ProtoEnumValueNode node) {
164-
return GoogRequire.create(node.getType().getNameForBackend(SoyBackendKind.JS_SRC))
165-
.googModuleGet()
166-
.dotAccess(Ascii.toUpperCase(node.getEnumValueDescriptor().getName()));
166+
String module;
167+
String valueName;
168+
if (node.getType() instanceof SoyProtoEnumType enumType) {
169+
module = enumType.getNameForBackend(SoyBackendKind.JS_SRC);
170+
valueName = node.getEnumValueDescriptor().getName();
171+
} else if (node.getType() instanceof SoyProtoOneofCaseEnumType oneofCaseType) {
172+
module = oneofCaseType.getNameForBackend(SoyBackendKind.JS_SRC);
173+
valueName = oneofCaseType.getNameForValue((int) node.getValue());
174+
} else {
175+
throw new AssertionError("Unexpected type: " + node.getType());
176+
}
177+
178+
return GoogRequire.create(module).googModuleGet().dotAccess(Ascii.toUpperCase(valueName));
167179
}
168180
}

java/src/com/google/template/soy/jbcsrc/ExpressionCompiler.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,12 @@ private SoyExpression visitMethodCall(SoyExpression baseExpr, MethodCallNode nod
17231723
ProtoUtils.SingularFieldAccessMode.DEFAULT_IF_UNSET,
17241724
varManager,
17251725
Int64ConversionMode.FOLLOW_JS_TYPE);
1726+
case GET_PROTO_ONEOF_CASE:
1727+
return ProtoUtils.accessOneofCase(
1728+
baseExpr,
1729+
BuiltinMethod.getProtoFieldNameFromMethodCall(node),
1730+
node.getType(),
1731+
varManager);
17261732
case GET_PROTO_FIELD:
17271733
return ProtoUtils.accessField(
17281734
baseExpr,

java/src/com/google/template/soy/jbcsrc/ProtoUtils.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static com.google.template.soy.jbcsrc.restricted.BytecodeUtils.newLabel;
3434
import static com.google.template.soy.jbcsrc.restricted.BytecodeUtils.numericConversion;
3535

36+
import com.google.common.base.CaseFormat;
3637
import com.google.common.collect.ImmutableList;
3738
import com.google.common.collect.ImmutableMap;
3839
import com.google.common.collect.ImmutableSet;
@@ -257,6 +258,55 @@ static SoyExpression accessField(
257258
}
258259
}
259260

261+
static SoyExpression accessOneofCase(
262+
SoyExpression baseExpr,
263+
String oneofName,
264+
SoyType fieldType,
265+
LocalVariableManager varManager) {
266+
SoyType type = baseExpr.soyType().getEffectiveType();
267+
if (type instanceof SoyProtoType soyProtoType) {
268+
return accessOneofCase(soyProtoType, baseExpr, oneofName);
269+
} else {
270+
return accessProtoUnionField(
271+
baseExpr,
272+
oneofName,
273+
fieldType,
274+
varManager,
275+
expr -> {
276+
SoyProtoType protoType = expr.soyType().asType(SoyProtoType.class);
277+
return accessOneofCase(protoType, expr, oneofName);
278+
});
279+
}
280+
}
281+
282+
private static SoyExpression accessOneofCase(
283+
SoyProtoType protoType, SoyExpression baseExpr, String oneofName) {
284+
OneofDescriptor descriptor =
285+
protoType.getDescriptor().getOneofs().stream()
286+
.filter(
287+
o ->
288+
CaseFormat.LOWER_UNDERSCORE
289+
.to(CaseFormat.LOWER_CAMEL, o.getName())
290+
.equals(oneofName))
291+
.findFirst()
292+
.get();
293+
SoyRuntimeType unboxedRuntimeType = SoyRuntimeType.getUnboxedType(protoType).get();
294+
Expression typedBaseExpr = baseExpr.unboxAsMessageUnchecked(unboxedRuntimeType.runtimeType());
295+
MethodRef getCaseMethod = getOneOfCaseMethod(descriptor);
296+
Expression invokeGetCase = typedBaseExpr.invoke(getCaseMethod);
297+
298+
MethodRef getNumberMethod =
299+
MethodRef.createInstanceMethod(
300+
TypeInfo.createClass(JavaQualifiedNames.getCaseEnumClassName(descriptor)),
301+
new Method("getNumber", Type.INT_TYPE, MethodRef.NO_METHOD_ARGS),
302+
MethodPureness.PURE);
303+
304+
Expression getNumber = invokeGetCase.invoke(getNumberMethod);
305+
Expression longNumber = numericConversion(getNumber, Type.LONG_TYPE);
306+
307+
return SoyExpression.forInt(longNumber);
308+
}
309+
260310
/**
261311
* Returns a {@link SoyExpression} for accessing a field of a proto, limited to a single type of
262312
* proto if the baseExpr can be a union of protos.

java/src/com/google/template/soy/jssrc/internal/NullSafeAccumulator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3232
import com.google.errorprone.annotations.ForOverride;
3333
import com.google.protobuf.Descriptors.FieldDescriptor;
34+
import com.google.protobuf.Descriptors.OneofDescriptor;
3435
import com.google.template.soy.internal.proto.ProtoUtils;
3536
import com.google.template.soy.jssrc.dsl.CodeChunk;
3637
import com.google.template.soy.jssrc.dsl.Expression;
@@ -478,6 +479,7 @@ private enum Type {
478479
GET("get", ""),
479480
GET_OR_UNDEFINED("get", "OrUndefined"),
480481
GET_READONLY("getReadonly", ""),
482+
GET_ONEOF_CASE("get", "Case"),
481483
HAS("has", "");
482484

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

543+
static ProtoCall getOneofCase(String oneofName, OneofDescriptor desc) {
544+
String getter = "get" + LOWER_CAMEL.to(UPPER_CAMEL, oneofName) + "Case";
545+
return new AutoValue_NullSafeAccumulator_ProtoCall(
546+
getter, /* getterArg= */ null, /* accessType= */ null, /* unpackFunction= */ null);
547+
}
548+
541549
private static ProtoCall accessor(String fieldName, FieldDescriptor desc, Type type) {
542550
String getter;
543551
Expression arg;

java/src/com/google/template/soy/jssrc/internal/TranslateExprNodeVisitor.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import static com.google.template.soy.passes.ContentSecurityPolicyNonceInjectionPass.CSP_STYLE_NONCE_VARIABLE_NAME;
6969

7070
import com.google.common.base.Ascii;
71+
import com.google.common.base.CaseFormat;
7172
import com.google.common.base.Joiner;
7273
import com.google.common.base.Preconditions;
7374
import com.google.common.collect.ImmutableList;
@@ -76,6 +77,7 @@
7677
import com.google.common.collect.Iterables;
7778
import com.google.protobuf.Descriptors.Descriptor;
7879
import com.google.protobuf.Descriptors.FieldDescriptor;
80+
import com.google.protobuf.Descriptors.OneofDescriptor;
7981
import com.google.template.soy.base.SourceLocation;
8082
import com.google.template.soy.base.SoyBackendKind;
8183
import com.google.template.soy.base.internal.Identifier;
@@ -153,6 +155,8 @@
153155
import com.google.template.soy.types.ListType;
154156
import com.google.template.soy.types.MapType;
155157
import com.google.template.soy.types.NumberType;
158+
import com.google.template.soy.types.SoyProtoEnumType;
159+
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
156160
import com.google.template.soy.types.SoyProtoType;
157161
import com.google.template.soy.types.SoyType;
158162
import com.google.template.soy.types.SoyTypes;
@@ -344,10 +348,23 @@ protected Expression visitStringNode(StringNode node) {
344348

345349
@Override
346350
protected Expression visitProtoEnumValueNode(ProtoEnumValueNode node) {
347-
String module = node.getType().getNameForBackend(SoyBackendKind.JS_SRC);
351+
String module;
352+
String valueName;
353+
if (node.getType() instanceof SoyProtoEnumType) {
354+
SoyProtoEnumType enumType = (SoyProtoEnumType) node.getType();
355+
module = enumType.getNameForBackend(SoyBackendKind.JS_SRC);
356+
valueName = node.getEnumValueDescriptor().getName();
357+
} else if (node.getType() instanceof SoyProtoOneofCaseEnumType) {
358+
SoyProtoOneofCaseEnumType oneofCaseType = (SoyProtoOneofCaseEnumType) node.getType();
359+
module = oneofCaseType.getNameForBackend(SoyBackendKind.JS_SRC);
360+
valueName = oneofCaseType.getNameForValue((int) node.getValue());
361+
} else {
362+
throw new AssertionError("Unexpected type: " + node.getType());
363+
}
364+
348365
return GoogRequire.create(module)
349366
.reference()
350-
.dotAccess(Id.create(Ascii.toUpperCase(node.getEnumValueDescriptor().getName())));
367+
.dotAccess(Id.create(Ascii.toUpperCase(valueName)));
351368
}
352369

353370
// -----------------------------------------------------------------------------------------------
@@ -789,6 +806,27 @@ private NullSafeAccumulator genCodeForMethodCall(
789806
ProtoCall.getReadonlyField(
790807
fieldName, ((SoyProtoType) type).getFieldDescriptor(fieldName)));
791808
return base.dotAccess(fieldAccess, nullSafe);
809+
case GET_PROTO_ONEOF_CASE:
810+
String oneofName = BuiltinMethod.getProtoFieldNameFromMethodCall(methodCallNode);
811+
fieldAccess =
812+
genCodeForMaybeUnion(
813+
baseType,
814+
oneofName,
815+
sourceLocation,
816+
type -> {
817+
SoyProtoType protoType = (SoyProtoType) type;
818+
OneofDescriptor oneof =
819+
protoType.getDescriptor().getOneofs().stream()
820+
.filter(
821+
o ->
822+
CaseFormat.LOWER_UNDERSCORE
823+
.to(CaseFormat.LOWER_CAMEL, o.getName())
824+
.equals(oneofName))
825+
.findFirst()
826+
.get();
827+
return ProtoCall.getOneofCase(oneofName, oneof);
828+
});
829+
return base.dotAccess(fieldAccess, nullSafe);
792830
// When adding new built-in methods it may be necessary to assert that the base expression
793831
// is not null in order to prevent a method call on a null instance from ever succeeding.
794832
case MAP_GET:

java/src/com/google/template/soy/passes/ResolveDottedImportsPass.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.protobuf.Descriptors.EnumDescriptor;
2222
import com.google.protobuf.Descriptors.EnumValueDescriptor;
23+
import com.google.protobuf.Descriptors.OneofDescriptor;
2324
import com.google.template.soy.base.SourceLocation;
2425
import com.google.template.soy.base.internal.IdGenerator;
2526
import com.google.template.soy.base.internal.Identifier;
@@ -48,7 +49,9 @@
4849
import com.google.template.soy.types.NamespaceType;
4950
import com.google.template.soy.types.ProtoEnumImportType;
5051
import com.google.template.soy.types.ProtoImportType;
52+
import com.google.template.soy.types.ProtoOneofCaseImportType;
5153
import com.google.template.soy.types.SoyProtoEnumType;
54+
import com.google.template.soy.types.SoyProtoOneofCaseEnumType;
5255
import com.google.template.soy.types.SoyType;
5356
import com.google.template.soy.types.StringType;
5457
import com.google.template.soy.types.TemplateImportType;
@@ -175,15 +178,28 @@ private ExprNode resolveField(
175178
SoyType type = defn.type();
176179

177180
if (defn.getSymbolKind() == SymbolKind.PROTO_ENUM) {
178-
EnumDescriptor enumDescriptor = ((ProtoEnumImportType) type).getDescriptor();
179-
EnumValueDescriptor val = enumDescriptor.findValueByName(fieldName);
180181
Identifier id = Identifier.create(refn.getName() + "." + fieldName, fullLocation);
181-
SoyProtoEnumType soyType = typeRegistry.getOrCreateProtoEnumType(enumDescriptor);
182-
if (val != null) {
183-
return new ProtoEnumValueNode(id, soyType, val.getNumber());
184-
} else {
185-
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
186-
return new ProtoEnumValueNode(id, soyType, 0);
182+
if (type instanceof ProtoEnumImportType) {
183+
EnumDescriptor enumDescriptor = ((ProtoEnumImportType) type).getDescriptor();
184+
EnumValueDescriptor val = enumDescriptor.findValueByName(fieldName);
185+
SoyProtoEnumType soyType = typeRegistry.getOrCreateProtoEnumType(enumDescriptor);
186+
if (val != null) {
187+
return new ProtoEnumValueNode(id, soyType, val.getNumber());
188+
} else {
189+
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
190+
return new ProtoEnumValueNode(id, soyType, 0);
191+
}
192+
} else if (type instanceof ProtoOneofCaseImportType protoOneofCaseImportType) {
193+
OneofDescriptor oneofDescriptor = protoOneofCaseImportType.getDescriptor();
194+
SoyProtoOneofCaseEnumType soyType =
195+
typeRegistry.getOrCreateProtoOneofCaseEnumType(oneofDescriptor);
196+
Integer val = soyType.getValue(fieldName);
197+
if (val != null) {
198+
return new ProtoEnumValueNode(id, soyType, val);
199+
} else {
200+
errorReporter.report(fullLocation, ENUM_MEMBERSHIP_ERROR, fieldName, refn.getName());
201+
return new ProtoEnumValueNode(id, soyType, 0);
202+
}
187203
}
188204
}
189205

0 commit comments

Comments
 (0)