Search in sources :

Example 1 with SoyType

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();
}
Also used : ParentExprNode(com.google.template.soy.exprtree.ExprNode.ParentExprNode) ExprNode(com.google.template.soy.exprtree.ExprNode) ParentExprNode(com.google.template.soy.exprtree.ExprNode.ParentExprNode) VarRefNode(com.google.template.soy.exprtree.VarRefNode) SoyType(com.google.template.soy.types.SoyType) HashSet(java.util.HashSet)

Example 2 with SoyType

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);
    }
}
Also used : UnionType(com.google.template.soy.types.UnionType) EnumSet(java.util.EnumSet) LinkedHashSet(java.util.LinkedHashSet) ImmutableSortedSet(com.google.common.collect.ImmutableSortedSet) ImmutableSet(com.google.common.collect.ImmutableSet) Set(java.util.Set) LegacyObjectMapType(com.google.template.soy.types.LegacyObjectMapType) MapType(com.google.template.soy.types.MapType) RecordType(com.google.template.soy.types.RecordType) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) ListType(com.google.template.soy.types.ListType) Kind(com.google.template.soy.types.SoyType.Kind) SoyBackendKind(com.google.template.soy.base.SoyBackendKind) SanitizedContentKind(com.google.template.soy.base.internal.SanitizedContentKind) Optional(com.google.common.base.Optional) LegacyObjectMapType(com.google.template.soy.types.LegacyObjectMapType) SoyProtoType(com.google.template.soy.types.SoyProtoType) SoyType(com.google.template.soy.types.SoyType) SoyProtoEnumType(com.google.template.soy.types.SoyProtoEnumType) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) EnumMap(java.util.EnumMap) Generator(com.google.template.soy.jssrc.dsl.CodeChunk.Generator)

Example 3 with 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();
}
Also used : LinkedHashMap(java.util.LinkedHashMap) IndirectParamsInfo(com.google.template.soy.passes.FindIndirectParamsVisitor.IndirectParamsInfo) GoogRequire(com.google.template.soy.jssrc.dsl.GoogRequire) SoyType(com.google.template.soy.types.SoyType) TemplateParam(com.google.template.soy.soytree.defn.TemplateParam) HashSet(java.util.HashSet) FindIndirectParamsVisitor(com.google.template.soy.passes.FindIndirectParamsVisitor)

Example 4 with SoyType

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;
}
Also used : CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) SoyType(com.google.template.soy.types.SoyType)

Example 5 with SoyType

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());
}
Also used : ImmutableList(com.google.common.collect.ImmutableList) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) SoyType(com.google.template.soy.types.SoyType) VariableDeclaration(com.google.template.soy.jssrc.dsl.VariableDeclaration) TemplateParam(com.google.template.soy.soytree.defn.TemplateParam) CheckReturnValue(javax.annotation.CheckReturnValue)

Aggregations

SoyType (com.google.template.soy.types.SoyType)26 MapType (com.google.template.soy.types.MapType)5 ExprNode (com.google.template.soy.exprtree.ExprNode)4 SoyExpression (com.google.template.soy.jbcsrc.restricted.SoyExpression)4 CodeChunk (com.google.template.soy.jssrc.dsl.CodeChunk)4 LegacyObjectMapType (com.google.template.soy.types.LegacyObjectMapType)4 ListType (com.google.template.soy.types.ListType)4 LinkedHashMap (java.util.LinkedHashMap)4 FieldDescriptor (com.google.protobuf.Descriptors.FieldDescriptor)3 ParentExprNode (com.google.template.soy.exprtree.ExprNode.ParentExprNode)3 TemplateParam (com.google.template.soy.soytree.defn.TemplateParam)3 SoyProtoEnumType (com.google.template.soy.types.SoyProtoEnumType)3 SoyProtoType (com.google.template.soy.types.SoyProtoType)3 UnionType (com.google.template.soy.types.UnionType)3 HashSet (java.util.HashSet)3 Map (java.util.Map)3 ImmutableMap (com.google.common.collect.ImmutableMap)2 FunctionNode (com.google.template.soy.exprtree.FunctionNode)2 VarRefNode (com.google.template.soy.exprtree.VarRefNode)2 RecordType (com.google.template.soy.types.RecordType)2