use of com.google.template.soy.types.SoyType in project closure-templates by google.
the class ExpressionCompilerTest method createTemplateBody.
private String createTemplateBody(String soyExpr) {
// collect all varrefs and apply them as template parameters. This way all varrefs have a valid
// vardef
// TODO(lukes): this logic would be useful in a lot of tests and potentially unblock efforts to
// eliminate UNDECLARED vars
ExprNode expr = SoyFileParser.parseExpression(soyExpr, PluginResolver.nullResolver(Mode.ALLOW_UNDEFINED, ErrorReporter.exploding()), ErrorReporter.exploding());
final StringBuilder templateBody = new StringBuilder();
new AbstractExprNodeVisitor<Void>() {
final Set<String> names = new HashSet<>();
@Override
protected void visitVarRefNode(VarRefNode node) {
if (names.add(node.getName())) {
SoyType type = variables.get(node.getName()).soyType();
templateBody.append("{@param ").append(node.getName()).append(": ").append(type).append("}\n");
}
}
@Override
protected void visitExprNode(ExprNode node) {
if (node instanceof ParentExprNode) {
visitChildren((ParentExprNode) node);
}
}
}.exec(expr);
templateBody.append("{" + soyExpr + "}\n");
return templateBody.toString();
}
use of com.google.template.soy.types.SoyType in project closure-templates by google.
the class JsType method forSoyType.
/**
* Returns a {@link JsType} corresponding to the given {@link SoyType}
*
* <p>TODO(lukes): consider adding a cache for all the computed types. The same type is probably
* accessed many many times.
*
* @param soyType the soy type
* @param isIncrementalDom whether or not this is for incremental dom.
*/
static JsType forSoyType(SoyType soyType, boolean isIncrementalDom) {
switch(soyType.getKind()) {
case NULL:
return NULL_OR_UNDEFINED_TYPE;
case ANY:
return ANY_TYPE;
case UNKNOWN:
return UNKNOWN_TYPE;
case BOOL:
return BOOLEAN_TYPE;
case PROTO_ENUM:
SoyProtoEnumType enumType = (SoyProtoEnumType) soyType;
String enumTypeName = enumType.getNameForBackend(SoyBackendKind.JS_SRC);
return builder().addType("number").addType(enumTypeName).addRequire(GoogRequire.create(enumTypeName)).setPredicate(GOOG_IS_NUMBER).build();
case FLOAT:
case INT:
return NUMBER_TYPE;
case STRING:
return STRING_OR_UNSANITIZED_TEXT;
case ATTRIBUTES:
if (isIncrementalDom) {
// idom has a different strategy for handling these
return IDOM_ATTRIBUTES;
}
// fall through
case HTML:
if (isIncrementalDom) {
// idom has a different strategy for handling these
return IDOM_HTML;
}
// fall-through
case CSS:
case JS:
case URI:
case TRUSTED_RESOURCE_URI:
return STRICT_TYPES.get(((SanitizedType) soyType).getContentKind());
case LIST:
ListType listType = (ListType) soyType;
if (listType.getElementType().getKind() == SoyType.Kind.ANY) {
return RAW_ARRAY_TYPE;
}
JsType element = forSoyType(listType.getElementType(), isIncrementalDom);
return builder().addType("!Array<" + element.typeExpr() + ">").addRequires(element.getGoogRequires()).setPredicate(GOOG_IS_ARRAY).build();
case LEGACY_OBJECT_MAP:
{
LegacyObjectMapType mapType = (LegacyObjectMapType) soyType;
if (mapType.getKeyType().getKind() == SoyType.Kind.ANY && mapType.getValueType().getKind() == SoyType.Kind.ANY) {
return RAW_OBJECT_TYPE;
}
JsType keyTypeName = forSoyType(mapType.getKeyType(), isIncrementalDom);
JsType valueTypeName = forSoyType(mapType.getValueType(), isIncrementalDom);
return builder().addType(String.format("!Object<%s,%s>", keyTypeName.typeExpr(), valueTypeName.typeExpr())).addRequires(keyTypeName.getGoogRequires()).addRequires(valueTypeName.getGoogRequires()).setPredicate(GOOG_IS_OBJECT).build();
}
case MAP:
{
MapType mapType = (MapType) soyType;
SoyType keyType = mapType.getKeyType();
SoyType.Kind keyKind = keyType.getKind();
Preconditions.checkState(MapType.isAllowedKeyType(keyType));
// Soy key type of string should translate to a JS key type of string.
// forSoyType(StringType.getInstance()) normally translates to
// string|!goog.soy.data.UnsanitizedText, but ES6 Maps always use instance equality for
// lookups. Using UnsanitizedText instances as keys in Soy maps would cause unexpected
// behavior (usually a failed map lookup), so don't generate signatures that allow it.
JsType keyTypeName = keyKind == SoyType.Kind.STRING ? STRING_TYPE : forSoyType(keyType, isIncrementalDom);
JsType valueTypeName = forSoyType(mapType.getValueType(), isIncrementalDom);
return builder().addType(String.format("!soy.map.Map<%s,%s>", keyTypeName.typeExpr(), valueTypeName.typeExpr())).addRequires(keyTypeName.getGoogRequires()).addRequires(valueTypeName.getGoogRequires()).addRequire(GoogRequire.create("soy.map")).setPredicate(TypePredicate.NO_OP).build();
}
case PROTO:
final SoyProtoType protoType = (SoyProtoType) soyType;
final String protoTypeName = protoType.getNameForBackend(SoyBackendKind.JS_SRC);
// isn't clear that this is very useful for users.
return builder().addType(protoTypeName).addRequire(GoogRequire.create(protoTypeName)).addCoercionStrategy(ValueCoercionStrategy.PROTO).setPredicate(new TypePredicate() {
@Override
public Optional<CodeChunk.WithValue> maybeCheck(CodeChunk.WithValue value, Generator codeGenerator) {
return Optional.of(value.instanceof_(JsRuntime.protoConstructor(protoType)));
}
}).build();
case RECORD:
{
RecordType recordType = (RecordType) soyType;
if (recordType.getMembers().isEmpty()) {
return RAW_OBJECT_TYPE;
}
Builder builder = builder();
Map<String, String> members = new LinkedHashMap<>();
for (Map.Entry<String, SoyType> member : recordType.getMembers().entrySet()) {
JsType forSoyType = forSoyType(member.getValue(), isIncrementalDom);
builder.addRequires(forSoyType.getGoogRequires());
members.put(member.getKey(), forSoyType.typeExprForRecordMember(/* isOptional= */
false));
}
return builder.addType("{" + Joiner.on(", ").withKeyValueSeparator(": ").join(members) + ",}").setPredicate(GOOG_IS_OBJECT).build();
}
case UNION:
{
UnionType unionType = (UnionType) soyType;
Builder builder = builder();
final Set<JsType> types = new LinkedHashSet<>();
final boolean isNullable = unionType.isNullable();
// handle null first so that if other type tests dereference the param they won't fail
if (isNullable) {
builder.addTypes(NULL_OR_UNDEFINED_TYPE.typeExpressions);
builder.addCoercionStrategy(ValueCoercionStrategy.NULL);
types.add(NULL_OR_UNDEFINED_TYPE);
}
for (SoyType member : unionType.getMembers()) {
if (member.getKind() == Kind.NULL) {
// handled above
continue;
}
JsType memberType = forSoyType(member, isIncrementalDom);
builder.addRequires(memberType.extraRequires);
builder.addTypes(memberType.typeExpressions);
builder.addCoercionStrategies(memberType.coercionStrategies);
types.add(memberType);
}
return builder.setPredicate(new TypePredicate() {
@Override
public Optional<CodeChunk.WithValue> maybeCheck(CodeChunk.WithValue value, Generator codeGenerator) {
CodeChunk.WithValue result = null;
// this automatically.
for (JsType memberType : types) {
Optional<CodeChunk.WithValue> typeAssertion = memberType.getTypeAssertion(value, codeGenerator);
if (!typeAssertion.isPresent()) {
return Optional.absent();
}
if (result == null) {
result = typeAssertion.get();
} else {
result = result.or(typeAssertion.get(), codeGenerator);
}
}
return Optional.of(result);
}
}).build();
}
default:
throw new AssertionError("unhandled soytype: " + soyType);
}
}
use of com.google.template.soy.types.SoyType in project closure-templates by google.
the class GenJsCodeVisitor method genParamsRecordType.
// -----------------------------------------------------------------------------------------------
// Helpers
/**
* Generate the JSDoc for the opt_data parameter.
*/
private String genParamsRecordType(TemplateNode node) {
Set<String> paramNames = new HashSet<>();
// Generate members for explicit params.
Map<String, String> record = new LinkedHashMap<>();
for (TemplateParam param : node.getParams()) {
JsType jsType = getJsType(param.type());
record.put(genParamAlias(param.name()), jsType.typeExprForRecordMember(/* isOptional= */
!param.isRequired()));
for (GoogRequire require : jsType.getGoogRequires()) {
jsCodeBuilder.addGoogRequire(require);
}
paramNames.add(param.name());
}
// Do the same for indirect params, if we can find them.
// If there's a conflict between the explicitly-declared type, and the type
// inferred from the indirect params, then the explicit type wins.
// Also note that indirect param types may not be inferrable if the target
// is not in the current compilation file set.
IndirectParamsInfo ipi = new FindIndirectParamsVisitor(templateRegistry).exec(node);
// types in JS allow additional undeclared fields to be present.
if (!ipi.mayHaveIndirectParamsInExternalCalls && !ipi.mayHaveIndirectParamsInExternalDelCalls) {
for (String indirectParamName : ipi.indirectParamTypes.keySet()) {
if (paramNames.contains(indirectParamName)) {
continue;
}
Collection<SoyType> paramTypes = ipi.indirectParamTypes.get(indirectParamName);
SoyType combinedType = SoyTypes.computeLowestCommonType(typeRegistry, paramTypes);
// Note that Union folds duplicate types and flattens unions, so if
// the combinedType is already a union this will do the right thing.
// TODO: detect cases where nullable is not needed (requires flow
// analysis to determine if the template is always called.)
SoyType indirectParamType = typeRegistry.getOrCreateUnionType(combinedType, NullType.getInstance());
JsType jsType = getJsType(indirectParamType);
// NOTE: we do not add goog.requires for indirect types. This is because it might introduce
// strict deps errors. This should be fine though since the transitive soy template that
// actually has the param will add them.
record.put(genParamAlias(indirectParamName), jsType.typeExprForRecordMember(/* isOptional= */
true));
}
}
StringBuilder sb = new StringBuilder();
sb.append("{\n * ");
Joiner.on(",\n * ").withKeyValueSeparator(": ").appendTo(sb, record);
// trailing comma in record is important in case the last record member is the
// unknown type
sb.append(",\n * }");
return sb.toString();
}
use of com.google.template.soy.types.SoyType in project closure-templates by google.
the class GenJsCodeVisitor method coerceTypeForSwitchComparison.
// js switch statements use === for comparing the switch expr to the cases. In order to preserve
// soy equality semantics for sanitized content objects we need to coerce cases and switch exprs
// to strings.
private CodeChunk.WithValue coerceTypeForSwitchComparison(ExprRootNode expr) {
CodeChunk.WithValue switchOn = translateExpr(expr);
SoyType type = expr.getType();
// If the type is possibly a sanitized content type then we need to toString it.
if (SoyTypes.makeNullable(StringType.getInstance()).isAssignableFrom(type) || type.equals(AnyType.getInstance()) || type.equals(UnknownType.getInstance())) {
CodeChunk.Generator codeGenerator = templateTranslationContext.codeGenerator();
CodeChunk.WithValue tmp = codeGenerator.declarationBuilder().setRhs(switchOn).build().ref();
return CodeChunk.ifExpression(GOOG_IS_OBJECT.call(tmp), tmp.dotAccess("toString").call()).else_(tmp).build(codeGenerator);
}
// have reasonably defined behavior.
return switchOn;
}
use of com.google.template.soy.types.SoyType in project closure-templates by google.
the class GenJsCodeVisitor method genParamTypeChecks.
/**
* Generate code to verify the runtime types of the input params. Also typecasts the input
* parameters and assigns them to local variables for use in the template.
*
* @param node the template node.
*/
@CheckReturnValue
protected CodeChunk genParamTypeChecks(TemplateNode node) {
ImmutableList.Builder<CodeChunk> declarations = ImmutableList.builder();
for (TemplateParam param : node.getAllParams()) {
if (param.declLoc() != TemplateParam.DeclLoc.HEADER) {
continue;
}
String paramName = param.name();
SoyType paramType = param.type();
CodeChunk.Generator generator = templateTranslationContext.codeGenerator();
CodeChunk.WithValue paramChunk = TranslateExprNodeVisitor.genCodeForParamAccess(paramName, param.isInjected());
JsType jsType = getJsType(paramType);
// The opt_param.name value that will be type-tested.
String paramAlias = genParamAlias(paramName);
CodeChunk.WithValue coerced = jsType.getValueCoercion(paramChunk, generator);
if (coerced != null) {
// since we have coercion logic, dump into a temporary
paramChunk = generator.declarationBuilder().setRhs(coerced).build().ref();
}
// The param value to assign
CodeChunk.WithValue value;
Optional<CodeChunk.WithValue> typeAssertion = jsType.getTypeAssertion(paramChunk, generator);
// The type-cast expression.
if (typeAssertion.isPresent()) {
value = SOY_ASSERTS_ASSERT_TYPE.call(typeAssertion.get(), stringLiteral(paramName), paramChunk, stringLiteral(jsType.typeExpr()));
} else {
value = paramChunk;
}
VariableDeclaration.Builder declarationBuilder = VariableDeclaration.builder(paramAlias).setRhs(value).setGoogRequires(jsType.getGoogRequires());
declarationBuilder.setJsDoc("/** @type {" + jsType.typeExpr() + "} */");
VariableDeclaration declaration = declarationBuilder.build();
declarations.add(declaration);
templateTranslationContext.soyToJsVariableMappings().put(paramName, id(paramAlias));
}
return CodeChunk.statements(declarations.build());
}
Aggregations