Package com.redhat.ceylon.compiler.js

Source Code of com.redhat.ceylon.compiler.js.TypeUtils

package com.redhat.ceylon.compiler.js;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.redhat.ceylon.compiler.loader.MetamodelGenerator;
import com.redhat.ceylon.compiler.typechecker.model.Annotation;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Generic;
import com.redhat.ceylon.compiler.typechecker.model.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ParameterList;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.Setter;
import com.redhat.ceylon.compiler.typechecker.model.SiteVariance;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.model.Util;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;

/** A convenience class to help with the handling of certain type declarations. */
public class TypeUtils {

    final TypeDeclaration tuple;
    final TypeDeclaration iterable;
    final TypeDeclaration sequential;
    final TypeDeclaration _null;
    final TypeDeclaration empty;
    static final List<String> splitMetamodelAnnotations = Arrays.asList("ceylon.language::doc",
            "ceylon.language::throws", "ceylon.language::see", "ceylon.language::by");

    TypeUtils(Module languageModule) {
        final com.redhat.ceylon.compiler.typechecker.model.Package pkg = languageModule.getPackage(Module.LANGUAGE_MODULE_NAME);
        tuple = (TypeDeclaration)pkg.getDirectMember("Tuple", null, false);
        iterable = (TypeDeclaration)pkg.getDirectMember("Iterable", null, false);
        sequential = (TypeDeclaration)pkg.getDirectMember("Sequential", null, false);
        _null = (TypeDeclaration)pkg.getDirectMember("Null", null, false);
        empty = (TypeDeclaration)pkg.getDirectMember("Empty", null, false);
    }

    /** Prints the type arguments, usually for their reification. */
    public static void printTypeArguments(final Node node, final Map<TypeParameter,ProducedType> targs,
            final GenerateJsVisitor gen, final boolean skipSelfDecl, final Map<TypeParameter, SiteVariance> overrides) {
        gen.out("{");
        boolean first = true;
        for (Map.Entry<TypeParameter,ProducedType> e : targs.entrySet()) {
            if (first) {
                first = false;
            } else {
                gen.out(",");
            }
            gen.out(e.getKey().getName(), "$", e.getKey().getDeclaration().getName(), ":");
            final ProducedType pt = e.getValue();
            if (pt == null) {
                gen.out("'", e.getKey().getName(), "'");
            } else if (!outputTypeList(node, pt, gen, skipSelfDecl)) {
                boolean hasParams = pt.getTypeArgumentList() != null && !pt.getTypeArgumentList().isEmpty();
                boolean closeBracket = false;
                final TypeDeclaration d = pt.getDeclaration();
                if (d instanceof TypeParameter) {
                    resolveTypeParameter(node, (TypeParameter)d, gen, skipSelfDecl);
                } else {
                    closeBracket = pt.getDeclaration() instanceof TypeAlias==false;
                    if (closeBracket)gen.out("{t:");
                    outputQualifiedTypename(node,
                            node != null && gen.isImported(node.getUnit().getPackage(), pt.getDeclaration()),
                            pt, gen, skipSelfDecl);
                }
                if (hasParams) {
                    gen.out(",a:");
                    printTypeArguments(node, pt.getTypeArguments(), gen, skipSelfDecl, pt.getVarianceOverrides());
                }
                SiteVariance siteVariance = overrides == null ? null : overrides.get(e.getKey());
                if (siteVariance != null) {
                    gen.out(",", MetamodelGenerator.KEY_US_VARIANCE, ":");
                    if (siteVariance == SiteVariance.IN) {
                        gen.out("'in'");
                    } else {
                        gen.out("'out'");
                    }
                }
                if (closeBracket) {
                    gen.out("}");
                }
            }
        }
        gen.out("}");
    }

    static void outputQualifiedTypename(final Node node, final boolean imported, final ProducedType pt,
            final GenerateJsVisitor gen, final boolean skipSelfDecl) {
        TypeDeclaration t = pt.getDeclaration();
        final String qname = t.getQualifiedNameString();
        if (qname.equals("ceylon.language::Nothing")) {
            //Hack in the model means hack here as well
            gen.out(gen.getClAlias(), "Nothing");
        } else if (qname.equals("ceylon.language::null") || qname.equals("ceylon.language::Null")) {
            gen.out(gen.getClAlias(), "Null");
        } else if (pt.isUnknown()) {
            gen.out(gen.getClAlias(), "Anything");
        } else {
            final String modAlias = imported ? gen.getNames().moduleAlias(t.getUnit().getPackage().getModule()) : null;
            if (modAlias != null && !modAlias.isEmpty()) {
                gen.out(modAlias, ".");
            }
            if (t.getContainer() instanceof ClassOrInterface) {
                final Scope scope = node == null ? null : Util.getContainingClassOrInterface(node.getScope());
                List<ClassOrInterface> parents = new ArrayList<>();
                ClassOrInterface parent = (ClassOrInterface)t.getContainer();
                parents.add(0, parent);
                while (parent.getContainer() instanceof ClassOrInterface) {
                    parent = (ClassOrInterface)parent.getContainer();
                    parents.add(0, parent);
                }
                for (ClassOrInterface p : parents) {
                    if (p==scope) {
                        if (gen.opts.isOptimize()) {
                            gen.out(gen.getNames().self(p), ".");
                        }
                    } else {
                        gen.out(gen.getNames().name(p), ".");
                    }
                }
            }

            if (!outputTypeList(null, pt, gen, skipSelfDecl)) {
                gen.out(gen.getNames().name(t));
            }
        }
    }

    /** Prints out an object with a type constructor under the property "t" and its type arguments under
     * the property "a", or a union/intersection type with "u" or "i" under property "t" and the list
     * of types that compose it in an array under the property "l", or a type parameter as a reference to
     * already existing params. */
    static void typeNameOrList(final Node node, final ProducedType pt, final GenerateJsVisitor gen, final boolean skipSelfDecl) {
        TypeDeclaration type = pt.getDeclaration();
        if (!outputTypeList(node, pt, gen, skipSelfDecl)) {
            if (type instanceof TypeParameter) {
                resolveTypeParameter(node, (TypeParameter)type, gen, skipSelfDecl);
            } else if (type instanceof TypeAlias) {
                outputQualifiedTypename(node, node != null && gen.isImported(node.getUnit().getPackage(), type),
                        pt, gen, skipSelfDecl);
            } else {
                gen.out("{t:");
                outputQualifiedTypename(node, node != null && gen.isImported(node.getUnit().getPackage(), type),
                        pt, gen, skipSelfDecl);
                if (!pt.getTypeArgumentList().isEmpty()) {
                    final Map<TypeParameter,ProducedType> targs;
                    if (pt.getDeclaration().isToplevel()) {
                        targs = pt.getTypeArguments();
                    } else {
                        //Gather all type parameters from containers
                        Scope scope = node.getScope();
                        final HashSet<TypeParameter> parenttp = new HashSet<>();
                        while (scope != null) {
                            if (scope instanceof Generic) {
                                for (TypeParameter tp : ((Generic)scope).getTypeParameters()) {
                                    parenttp.add(tp);
                                }
                            }
                            scope = scope.getScope();
                        }
                        targs = new HashMap<>();
                        targs.putAll(pt.getTypeArguments());
                        Declaration cd = Util.getContainingDeclaration(pt.getDeclaration());
                        while (cd != null) {
                            if (cd instanceof Generic) {
                                for (TypeParameter tp : ((Generic)cd).getTypeParameters()) {
                                    if (parenttp.contains(tp)) {
                                        targs.put(tp, tp.getType());
                                    }
                                }
                            }
                            cd = Util.getContainingDeclaration(cd);
                        }
                    }
                    gen.out(",a:");
                    printTypeArguments(node, targs, gen, skipSelfDecl, pt.getVarianceOverrides());
                }
                gen.out("}");
            }
        }
    }

    /** Appends an object with the type's type and list of union/intersection types. */
    static boolean outputTypeList(final Node node, final ProducedType pt, final GenerateJsVisitor gen, final boolean skipSelfDecl) {
        TypeDeclaration d = pt.getDeclaration();
        final List<ProducedType> subs;
        if (d instanceof IntersectionType) {
            gen.out("{t:'i");
            subs = d.getSatisfiedTypes();
        } else if (d instanceof UnionType) {
            gen.out("{t:'u");
            subs = d.getCaseTypes();
        } else if ("ceylon.language::Tuple".equals(d.getQualifiedNameString())) {
            gen.out("{t:'T");
            subs = node.getUnit().getTupleElementTypes(pt);
            final ProducedType lastType = subs.get(subs.size()-1);
            if (node.getUnit().getSequenceDeclaration().equals(lastType.getDeclaration())
                    || node.getUnit().getSequentialDeclaration().equals(lastType.getDeclaration())) {
                //Non-empty, non-tuple tail; union it with its type parameter
                UnionType utail = new UnionType(node.getUnit());
                utail.setCaseTypes(Arrays.asList(lastType.getTypeArgumentList().get(0), lastType));
                subs.remove(subs.size()-1);
                subs.add(utail.getType());
            }
        } else {
            return false;
        }
        gen.out("',l:[");
        boolean first = true;
        for (ProducedType t : subs) {
            if (!first) gen.out(",");
            typeNameOrList(node, t, gen, skipSelfDecl);
            first = false;
        }
        gen.out("]}");
        return true;
    }

    /** Finds the owner of the type parameter and outputs a reference to the corresponding type argument. */
    static void resolveTypeParameter(final Node node, final TypeParameter tp,
            final GenerateJsVisitor gen, final boolean skipSelfDecl) {
        Scope parent = node.getScope();
        int outers = 0;
        while (parent != null && parent != tp.getContainer()) {
            if (parent instanceof TypeDeclaration && !((TypeDeclaration) parent).isAnonymous()) {
                outers++;
            }
            parent = parent.getScope();
        }
        if (tp.getContainer() instanceof ClassOrInterface) {
            if (parent == tp.getContainer()) {
                if (!skipSelfDecl) {
                    ClassOrInterface ontoy = Util.getContainingClassOrInterface(node.getScope());
                    while (ontoy.isAnonymous())ontoy=Util.getContainingClassOrInterface(ontoy.getScope());
                    gen.out(gen.getNames().self((TypeDeclaration)ontoy));
                    for (int i = 0; i < outers; i++) {
                        gen.out(".outer$");
                    }
                    gen.out(".");
                }
                gen.out("$$targs$$.", tp.getName(), "$", tp.getDeclaration().getName());
            } else {
                //This can happen in expressions such as Singleton(n) when n is dynamic
                gen.out("{/*NO PARENT*/t:", gen.getClAlias(), "Anything}");
            }
        } else if (tp.getContainer() instanceof TypeAlias) {
            if (parent == tp.getContainer()) {
                gen.out("'", tp.getName(), "$", tp.getDeclaration().getName(), "'");
            } else {
                //This can happen in expressions such as Singleton(n) when n is dynamic
                gen.out("{/*NO PARENT ALIAS*/t:", gen.getClAlias(), "Anything}");
            }
        } else {
            //it has to be a method, right?
            //We need to find the index of the parameter where the argument occurs
            //...and it could be null...
            int plistCount = -1;
            ProducedType type = null;
            for (Iterator<ParameterList> iter0 = ((Method)tp.getContainer()).getParameterLists().iterator();
                    type == null && iter0.hasNext();) {
                plistCount++;
                for (Iterator<Parameter> iter1 = iter0.next().getParameters().iterator();
                        type == null && iter1.hasNext();) {
                    if (type == null) {
                        type = typeContainsTypeParameter(iter1.next().getType(), tp);
                    }
                }
            }
            //The ProducedType that we find corresponds to a parameter, whose type can be:
            //A type parameter in the method, in which case we just use the argument's type (may be null)
            //A component of a union/intersection type, in which case we just use the argument's type (may be null)
            //A type argument of the argument's type, in which case we must get the reified generic from the argument
            if (tp.getContainer() == parent) {
                gen.out("$$$mptypes.", tp.getName(), "$", tp.getDeclaration().getName());
            } else {
                gen.out("/*METHOD TYPEPARM plist ", Integer.toString(plistCount), "#",
                        tp.getName(), "*/'", type.getProducedTypeQualifiedName(), "'");
            }
        }
    }

    static ProducedType typeContainsTypeParameter(ProducedType td, TypeParameter tp) {
        TypeDeclaration d = td.getDeclaration();
        if (d == tp) {
            return td;
        } else if (d instanceof UnionType || d instanceof IntersectionType) {
            List<ProducedType> comps = td.getCaseTypes();
            if (comps == null) comps = td.getSupertypes();
            for (ProducedType sub : comps) {
                td = typeContainsTypeParameter(sub, tp);
                if (td != null) {
                    return td;
                }
            }
        } else if (d instanceof ClassOrInterface) {
            for (ProducedType sub : td.getTypeArgumentList()) {
                if (typeContainsTypeParameter(sub, tp) != null) {
                    return td;
                }
            }
        }
        return null;
    }

    /** Find the type with the specified declaration among the specified type's supertypes, case types, satisfied types, etc. */
    static ProducedType findSupertype(TypeDeclaration d, ProducedType pt) {
        if (pt.getDeclaration().equals(d)) {
            return pt;
        }
        List<ProducedType> list = pt.getSupertypes() == null ? pt.getCaseTypes() : pt.getSupertypes();
        for (ProducedType t : list) {
            if (t.getDeclaration().equals(d)) {
                return t;
            }
        }
        return null;
    }

    static Map<TypeParameter, ProducedType> matchTypeParametersWithArguments(List<TypeParameter> params, List<ProducedType> targs) {
        if (params != null && targs != null && params.size() == targs.size()) {
            HashMap<TypeParameter, ProducedType> r = new HashMap<TypeParameter, ProducedType>();
            for (int i = 0; i < targs.size(); i++) {
                r.put(params.get(i), targs.get(i));
            }
            return r;
        }
        return null;
    }

    Map<TypeParameter, ProducedType> wrapAsIterableArguments(ProducedType pt) {
        HashMap<TypeParameter, ProducedType> r = new HashMap<TypeParameter, ProducedType>();
        r.put(iterable.getTypeParameters().get(0), pt);
        r.put(iterable.getTypeParameters().get(1), _null.getType());
        return r;
    }

    static boolean isUnknown(Declaration d) {
        return d == null || d.getQualifiedNameString().equals("UnknownType");
    }

    static void spreadArrayCheck(final Tree.Term term, final GenerateJsVisitor gen) {
        String tmp = gen.getNames().createTempVariable();
        gen.out("(", tmp, "=");
        term.visit(gen);
        gen.out(",Array.isArray(", tmp, ")?", tmp);
        gen.out(":function(){throw new TypeError('Expected JS Array (",
                term.getUnit().getFilename(), " ", term.getLocation(), ")')}())");
    }

    /** Generates the code to throw an Exception if a dynamic object is not of the specified type. */
    static void generateDynamicCheck(final Tree.Term term, ProducedType t,
            final GenerateJsVisitor gen, final boolean skipSelfDecl,
            final Map<TypeParameter,ProducedType> typeArguments) {
        if (t.getDeclaration().isDynamic()) {
            gen.out(gen.getClAlias(), "dre$$(");
            term.visit(gen);
            gen.out(",");
            TypeUtils.typeNameOrList(term, t, gen, skipSelfDecl);
            gen.out(",'", term.getUnit().getFilename(), " ", term.getLocation(), "')");
        } else {
            final boolean checkFloat = term.getUnit().getFloatDeclaration().equals(t.getDeclaration());
            final boolean checkInt = checkFloat ? false : term.getUnit().getIntegerDeclaration().equals(t.getDeclaration());
            String tmp = gen.getNames().createTempVariable();
            gen.out("(", tmp, "=");
            term.visit(gen);
            final String errmsg;
            if (checkFloat) {
                gen.out(",typeof(", tmp, ")==='number'?", gen.getClAlias(), "Float(", tmp, ")");
                errmsg = "Expected Float";
            } else if (checkInt) {
                gen.out(",typeof(", tmp, ")==='number'?Math.floor(", tmp, ")");
                errmsg = "Expected Integer";
            } else {
                gen.out(",", gen.getClAlias(), "is$(", tmp, ",");
                if (t.getDeclaration() instanceof TypeParameter && typeArguments != null
                        && typeArguments.containsKey(t.getDeclaration())) {
                    t = typeArguments.get(t.getDeclaration());
                }
                TypeUtils.typeNameOrList(term, t, gen, skipSelfDecl);
                gen.out(")?", tmp);
                errmsg = "Expected " + t.getProducedTypeQualifiedName();
            }
            gen.out(":function(){throw new TypeError('", errmsg, " (",
                    term.getUnit().getFilename(), " ", term.getLocation(), ")')}())");
        }
    }

    static void encodeParameterListForRuntime(Node n, ParameterList plist, GenerateJsVisitor gen) {
        boolean first = true;
        gen.out("[");
        for (Parameter p : plist.getParameters()) {
            if (first) first=false; else gen.out(",");
            gen.out("{", MetamodelGenerator.KEY_NAME, ":'", p.getName(), "',");
            gen.out(MetamodelGenerator.KEY_METATYPE, ":'", MetamodelGenerator.METATYPE_PARAMETER, "',");
            if (p.getModel() instanceof Method) {
                gen.out("$pt:'f',");
            }
            if (p.isSequenced()) {
                gen.out("seq:1,");
            }
            if (p.isDefaulted()) {
                gen.out(MetamodelGenerator.KEY_DEFAULT, ":1,");
            }
            gen.out(MetamodelGenerator.KEY_TYPE, ":");
            metamodelTypeNameOrList(gen.getCurrentPackage(), p.getType(), gen);
            if (p.getModel().getAnnotations() != null && !p.getModel().getAnnotations().isEmpty()) {
                new ModelAnnotationGenerator(gen, p.getModel(), n).generateAnnotations();
            }
            gen.out("}");
        }
        gen.out("]");
    }

    /** Turns a Tuple type into a parameter list. */
    List<Parameter> convertTupleToParameters(ProducedType _tuple) {
        ArrayList<Parameter> rval = new ArrayList<>();
        int pos = 0;
        TypeDeclaration tdecl = _tuple.getDeclaration();
        while (!(empty.equals(tdecl) || tdecl instanceof TypeParameter)) {
            Parameter _p = null;
            if (tuple.equals(tdecl) || (tdecl.getCaseTypeDeclarations() != null
                    && tdecl.getCaseTypeDeclarations().size()==2
                    && tdecl.getCaseTypeDeclarations().contains(tuple))) {
                _p = new Parameter();
                _p.setModel(new Value());
                if (tuple.equals(tdecl)) {
                    _p.getModel().setType(_tuple.getTypeArgumentList().get(1));
                    _tuple = _tuple.getTypeArgumentList().get(2);
                } else {
                    //Handle union types for defaulted parameters
                    for (ProducedType mt : _tuple.getCaseTypes()) {
                        if (tuple.equals(mt.getDeclaration())) {
                            _p.getModel().setType(mt.getTypeArgumentList().get(1));
                            _tuple = mt.getTypeArgumentList().get(2);
                            break;
                        }
                    }
                    _p.setDefaulted(true);
                }
            } else if (tdecl.inherits(sequential)) {
                //Handle Sequence, for nonempty variadic parameters
                _p = new Parameter();
                _p.setModel(new Value());
                _p.getModel().setType(_tuple.getTypeArgumentList().get(0));
                _p.setSequenced(true);
                _tuple = empty.getType();
            }
            else {
                if (pos > 100) {
                    return rval;
                }
            }
            if (_tuple != null) tdecl = _tuple.getDeclaration();
            if (_p != null) {
                _p.setName("arg" + pos);
                rval.add(_p);
            }
            pos++;
        }
        return rval;
    }

    /** This method encodes the type parameters of a Tuple in the same way
     * as a parameter list for runtime. */
    private static void encodeTupleAsParameterListForRuntime(ProducedType _tuple, boolean nameAndMetatype, GenerateJsVisitor gen) {
        gen.out("[");
        int pos = 1;
        TypeDeclaration tdecl = _tuple.getDeclaration();
        while (!(gen.getTypeUtils().empty.equals(tdecl) || tdecl instanceof TypeParameter)) {
            if (pos > 1) gen.out(",");
            gen.out("{");
            pos++;
            if (nameAndMetatype) {
                gen.out(MetamodelGenerator.KEY_NAME, ":'p", Integer.toString(pos), "',");
                gen.out(MetamodelGenerator.KEY_METATYPE, ":'", MetamodelGenerator.METATYPE_PARAMETER, "',");
            }
            gen.out(MetamodelGenerator.KEY_TYPE, ":");
            if (gen.getTypeUtils().tuple.equals(tdecl) || (tdecl.getCaseTypeDeclarations() != null
                    && tdecl.getCaseTypeDeclarations().size()==2
                    && tdecl.getCaseTypeDeclarations().contains(gen.getTypeUtils().tuple))) {
                if (gen.getTypeUtils().tuple.equals(tdecl)) {
                    metamodelTypeNameOrList(gen.getCurrentPackage(), _tuple.getTypeArgumentList().get(1), gen);
                    _tuple = _tuple.getTypeArgumentList().get(2);
                } else {
                    //Handle union types for defaulted parameters
                    for (ProducedType mt : _tuple.getCaseTypes()) {
                        if (gen.getTypeUtils().tuple.equals(mt.getDeclaration())) {
                            metamodelTypeNameOrList(gen.getCurrentPackage(), mt.getTypeArgumentList().get(1), gen);
                            _tuple = mt.getTypeArgumentList().get(2);
                            break;
                        }
                    }
                    gen.out(",", MetamodelGenerator.KEY_DEFAULT,":1");
                }
            } else if (tdecl.inherits(gen.getTypeUtils().sequential)) {
                ProducedType _t2 = _tuple.getSupertype(gen.getTypeUtils().sequential);
                //Handle Sequence, for nonempty variadic parameters
                metamodelTypeNameOrList(gen.getCurrentPackage(), _t2.getTypeArgumentList().get(0), gen);
                gen.out(",seq:1");
                _tuple = gen.getTypeUtils().empty.getType();
            } else if (tdecl instanceof UnionType) {
                metamodelTypeNameOrList(gen.getCurrentPackage(), _tuple, gen);
                tdecl = gen.getTypeUtils().empty; _tuple=null;
            } else {
                gen.out("\n/*WARNING3! Tuple is actually ", _tuple.getProducedTypeQualifiedName(), ", ", tdecl.getName(),"*/");
                if (pos > 100) {
                    break;
                }
            }
            gen.out("}");
            if (_tuple != null) tdecl = _tuple.getDeclaration();
        }
        gen.out("]");
    }
    /** This method encodes the Arguments type argument of a Callable the same way
     * as a parameter list for runtime. */
    static void encodeCallableArgumentsAsParameterListForRuntime(ProducedType _callable, GenerateJsVisitor gen) {
        if (_callable.getCaseTypes() != null) {
            for (ProducedType pt : _callable.getCaseTypes()) {
                if (pt.getProducedTypeQualifiedName().startsWith("ceylon.language::Callable<")) {
                    _callable = pt;
                    break;
                }
            }
        } else if (_callable.getSatisfiedTypes() != null) {
            for (ProducedType pt : _callable.getSatisfiedTypes()) {
                if (pt.getProducedTypeQualifiedName().startsWith("ceylon.language::Callable<")) {
                    _callable = pt;
                    break;
                }
            }
        }
        if (!_callable.getProducedTypeQualifiedName().contains("ceylon.language::Callable<")) {
            gen.out("[/*WARNING1: got ", _callable.getProducedTypeQualifiedName(), " instead of Callable*/]");
            return;
        }
        List<ProducedType> targs = _callable.getTypeArgumentList();
        if (targs == null || targs.size() != 2) {
            gen.out("[/*WARNING2: missing argument types for Callable*/]");
            return;
        }
        encodeTupleAsParameterListForRuntime(targs.get(1), true, gen);
    }

    static void encodeForRuntime(Node that, final Declaration d, final GenerateJsVisitor gen) {
        if (d.getAnnotations() == null || d.getAnnotations().isEmpty()) {
            encodeForRuntime(that, d, gen, null);
        } else {
            encodeForRuntime(that, d, gen, new ModelAnnotationGenerator(gen, d, that));
        }
    }

    /** Output a metamodel map for runtime use. */
    static void encodeForRuntime(final Declaration d, final Tree.AnnotationList annotations, final GenerateJsVisitor gen) {
        encodeForRuntime(annotations, d, gen, new RuntimeMetamodelAnnotationGenerator() {
            @Override public void generateAnnotations() {
                outputAnnotationsFunction(annotations, d, gen);
            }
        });
    }

    /** Returns the list of keys to get from the package to the declaration, in the model. */
    public static List<String> generateModelPath(final Declaration d) {
        final ArrayList<String> sb = new ArrayList<>();
        final String pkgName = d.getUnit().getPackage().getNameAsString();
        sb.add(Module.LANGUAGE_MODULE_NAME.equals(pkgName)?"$":pkgName);
        if (d.isToplevel()) {
            sb.add(d.getName());
            if (d instanceof Setter) {
                sb.add("$set");
            }
        } else {
            Declaration p = d;
            final int i = sb.size();
            while (p instanceof Declaration) {
                if (p instanceof Setter) {
                    sb.add(i, "$set");
                }
                sb.add(i, TypeUtils.modelName(p));
                //Build the path in reverse
                if (!p.isToplevel()) {
                    if (p instanceof com.redhat.ceylon.compiler.typechecker.model.Class) {
                        sb.add(i, p.isAnonymous() ? MetamodelGenerator.KEY_OBJECTS : MetamodelGenerator.KEY_CLASSES);
                    } else if (p instanceof com.redhat.ceylon.compiler.typechecker.model.Interface) {
                        sb.add(i, MetamodelGenerator.KEY_INTERFACES);
                    } else if (p instanceof Method) {
                        sb.add(i, MetamodelGenerator.KEY_METHODS);
                    } else if (p instanceof TypeAlias || p instanceof Setter) {
                        sb.add(i, MetamodelGenerator.KEY_ATTRIBUTES);
                    } else { //It's a value
                        TypeDeclaration td=((TypedDeclaration)p).getTypeDeclaration();
                        sb.add(i, (td!=null&&td.isAnonymous())? MetamodelGenerator.KEY_OBJECTS
                                : MetamodelGenerator.KEY_ATTRIBUTES);
                    }
                }
                p = Util.getContainingDeclaration(p);
            }
        }
        return sb;
    }

    static void outputModelPath(final Declaration d, GenerateJsVisitor gen) {
        List<String> parts = generateModelPath(d);
        gen.out("[");
        boolean first = true;
        for (String p : parts) {
            if (p.startsWith("anonymous#"))continue;
            if (first)first=false;else gen.out(",");
            gen.out("'", p, "'");
        }
        gen.out("]");
    }

    static void encodeForRuntime(final Node that, final Declaration d, final GenerateJsVisitor gen,
            final RuntimeMetamodelAnnotationGenerator annGen) {
        gen.out("function(){return{mod:$CCMM$");
        List<TypeParameter> tparms = d instanceof Generic ? ((Generic)d).getTypeParameters() : null;
        List<ProducedType> satisfies = null;
        List<ProducedType> caseTypes = null;
        if (d instanceof com.redhat.ceylon.compiler.typechecker.model.Class) {
            com.redhat.ceylon.compiler.typechecker.model.Class _cd = (com.redhat.ceylon.compiler.typechecker.model.Class)d;
            if (_cd.getExtendedType() != null) {
                gen.out(",'super':");
                metamodelTypeNameOrList(d.getUnit().getPackage(), _cd.getExtendedType(), gen);
            }
            //Parameter types
            if (_cd.getParameterList()!=null) {
                gen.out(",", MetamodelGenerator.KEY_PARAMS, ":");
                encodeParameterListForRuntime(that, _cd.getParameterList(), gen);
            }
            satisfies = _cd.getSatisfiedTypes();
            caseTypes = _cd.getCaseTypes();

        } else if (d instanceof com.redhat.ceylon.compiler.typechecker.model.Interface) {

            satisfies = ((com.redhat.ceylon.compiler.typechecker.model.Interface) d).getSatisfiedTypes();
            caseTypes = ((com.redhat.ceylon.compiler.typechecker.model.Interface) d).getCaseTypes();

        } else if (d instanceof MethodOrValue) {

            gen.out(",", MetamodelGenerator.KEY_TYPE, ":");
            //This needs a new setting to resolve types but not type parameters
            metamodelTypeNameOrList(d.getUnit().getPackage(), ((MethodOrValue)d).getType(), gen);
            if (d instanceof Method) {
                gen.out(",", MetamodelGenerator.KEY_PARAMS, ":");
                //Parameter types of the first parameter list
                encodeParameterListForRuntime(that, ((Method)d).getParameterLists().get(0), gen);
                tparms = ((Method) d).getTypeParameters();
            }

        }
        if (!d.isToplevel()) {
            //Find the first container that is a Declaration
            Declaration _cont = Util.getContainingDeclaration(d);
            gen.out(",$cont:");
            boolean generateName = true;
            if (_cont.getName().startsWith("anonymous#")) {
                //Anon functions don't have metamodel so go up until we find a non-anon container
                Declaration _supercont = Util.getContainingDeclaration(_cont);
                while (_supercont != null && _supercont.getName().startsWith("anonymous#")) {
                    _supercont = Util.getContainingDeclaration(_supercont);
                }
                if (_supercont == null) {
                    //If the container is a package, add it because this isn't really toplevel
                    generateName = false;
                    gen.out("0");
                } else {
                    _cont = _supercont;
                }
            }
            if (generateName) {
                if (_cont instanceof Value) {
                    if (gen.defineAsProperty(_cont)) {
                        gen.qualify(that, _cont);
                        gen.out("$prop$");
                    }
                    gen.out(gen.getNames().getter(_cont));
                } else if (_cont instanceof Setter) {
                    gen.out("{setter:");
                    if (gen.defineAsProperty(_cont)) {
                        gen.qualify(that, _cont);
                        gen.out("$prop$", gen.getNames().getter(((Setter) _cont).getGetter()), ".set");
                    } else {
                        gen.out(gen.getNames().setter(((Setter) _cont).getGetter()));
                    }
                    gen.out("}");
                } else {
                    boolean inProto = gen.opts.isOptimize()
                            && (_cont.getContainer() instanceof TypeDeclaration);
                    final String path = gen.qualifiedPath(that, _cont, inProto);
                    if (path != null && !path.isEmpty()) {
                        gen.out(path, ".");
                    }
                    gen.out(gen.getNames().name(_cont));
                }
            }
        }
        if (tparms != null && !tparms.isEmpty()) {
            gen.out(",", MetamodelGenerator.KEY_TYPE_PARAMS, ":{");
            encodeTypeParametersForRuntime(d, tparms, true, gen);
            gen.out("}");
        }
        if (satisfies != null && !satisfies.isEmpty()) {
            gen.out(",", MetamodelGenerator.KEY_SATISFIES, ":[");
            boolean first = true;
            for (ProducedType st : satisfies) {
                if (!first)gen.out(",");
                first=false;
                metamodelTypeNameOrList(d.getUnit().getPackage(), st, gen);
            }
            gen.out("]");
        }
        if (caseTypes != null && !caseTypes.isEmpty()) {
            gen.out(",of:[");
            boolean first = true;
            for (ProducedType st : caseTypes) {
                if (!first)gen.out(",");
                first=false;
                if (st.getDeclaration().isAnonymous()) {
                    gen.out("$prop$", gen.getNames().getter(st.getDeclaration()));
                } else {
                    metamodelTypeNameOrList(d.getUnit().getPackage(), st, gen);
                }
            }
            gen.out("]");
        }
        if (annGen != null) {
            annGen.generateAnnotations();
        }
        //Path to its model
        gen.out(",d:");
        outputModelPath(d, gen);
        gen.out("};}");
    }

    static boolean encodeTypeParametersForRuntime(final Declaration d, final List<TypeParameter> tparms,
            boolean first, final GenerateJsVisitor gen) {
        for(TypeParameter tp : tparms) {
            boolean comma = false;
            if (!first)gen.out(",");
            first=false;
            gen.out(tp.getName(), "$", tp.getDeclaration().getName(), ":{");
            if (tp.isCovariant()) {
                gen.out(MetamodelGenerator.KEY_DS_VARIANCE, ":'out'");
                comma = true;
            } else if (tp.isContravariant()) {
                gen.out(MetamodelGenerator.KEY_DS_VARIANCE, ":'in'");
                comma = true;
            }
            List<ProducedType> typelist = tp.getSatisfiedTypes();
            if (typelist != null && !typelist.isEmpty()) {
                if (comma)gen.out(",");
                gen.out(MetamodelGenerator.KEY_SATISFIES, ":[");
                boolean first2 = true;
                for (ProducedType st : typelist) {
                    if (!first2)gen.out(",");
                    first2=false;
                    metamodelTypeNameOrList(d.getUnit().getPackage(), st, gen);
                }
                gen.out("]");
                comma = true;
            }
            typelist = tp.getCaseTypes();
            if (typelist != null && !typelist.isEmpty()) {
                if (comma)gen.out(",");
                gen.out("of:[");
                boolean first3 = true;
                for (ProducedType st : typelist) {
                    if (!first3)gen.out(",");
                    first3=false;
                    metamodelTypeNameOrList(d.getUnit().getPackage(), st, gen);
                }
                gen.out("]");
                comma = true;
            }
            if (tp.getDefaultTypeArgument() != null) {
                if (comma)gen.out(",");
                gen.out("def:");
                metamodelTypeNameOrList(d.getUnit().getPackage(), tp.getDefaultTypeArgument(), gen);
            }
            gen.out("}");
        }
        return first;
    }

    /** Prints out an object with a type constructor under the property "t" and its type arguments under
     * the property "a", or a union/intersection type with "u" or "i" under property "t" and the list
     * of types that compose it in an array under the property "l", or a type parameter as a reference to
     * already existing params. */
    static void metamodelTypeNameOrList(final com.redhat.ceylon.compiler.typechecker.model.Package pkg,
            ProducedType pt, GenerateJsVisitor gen) {
        if (pt == null) {
            //In dynamic blocks we sometimes get a null producedType
            pt = ((TypeDeclaration)pkg.getModule().getLanguageModule().getDirectPackage(
                    Module.LANGUAGE_MODULE_NAME).getDirectMember("Anything", null, false)).getType();
        }
        if (!outputMetamodelTypeList(pkg, pt, gen)) {
            TypeDeclaration type = pt.getDeclaration();
            if (type instanceof TypeParameter) {
                gen.out("'", type.getNameAsString(), "$", ((TypeParameter)type).getDeclaration().getName(), "'");
            } else if (type instanceof TypeAlias) {
                outputQualifiedTypename(null, gen.isImported(pkg, type), pt, gen, false);
            } else {
                gen.out("{t:");
                outputQualifiedTypename(null, gen.isImported(pkg, type), pt, gen, false);
                //Type Parameters
                if (!pt.getTypeArgumentList().isEmpty()) {
                    gen.out(",a:{");
                    boolean first = true;
                    for (Map.Entry<TypeParameter, ProducedType> e : pt.getTypeArguments().entrySet()) {
                        if (first) first=false; else gen.out(",");
                        gen.out(e.getKey().getNameAsString(), "$", e.getKey().getDeclaration().getName(), ":");
                        metamodelTypeNameOrList(pkg, e.getValue(), gen);
                    }
                    gen.out("}");
                }
                gen.out("}");
            }
        }
    }

    /** Appends an object with the type's type and list of union/intersection types; works only with union,
     * intersection and tuple types.
     * @return true if output was generated, false otherwise (it was a regular type) */
    static boolean outputMetamodelTypeList(final com.redhat.ceylon.compiler.typechecker.model.Package pkg,
            ProducedType pt, GenerateJsVisitor gen) {
        TypeDeclaration type = pt.getDeclaration();
        final List<ProducedType> subs;
        if (type instanceof IntersectionType) {
            gen.out("{t:'i");
            subs = type.getSatisfiedTypes();
        } else if (type instanceof UnionType) {
            //It still could be a Tuple with first optional type
            List<TypeDeclaration> cts = type.getCaseTypeDeclarations();
            if (cts.size()==2 && cts.contains(gen.getTypeUtils().empty) && cts.contains(gen.getTypeUtils().tuple)) {
                //yup...
                gen.out("{t:'T',l:");
                encodeTupleAsParameterListForRuntime(pt,false,gen);
                gen.out("}");
                return true;
            }
            gen.out("{t:'u");
            subs = type.getCaseTypes();
        } else if (type.getQualifiedNameString().equals("ceylon.language::Tuple")) {
            gen.out("{t:'T',l:");
            encodeTupleAsParameterListForRuntime(pt,false, gen);
            gen.out("}");
            return true;
        } else {
            return false;
        }
        gen.out("',l:[");
        boolean first = true;
        for (ProducedType t : subs) {
            if (!first) gen.out(",");
            metamodelTypeNameOrList(pkg, t, gen);
            first = false;
        }
        gen.out("]}");
        return true;
    }

    static String pathToModelDoc(final Declaration d) {
        if (d == null)return null;
        final StringBuilder sb = new StringBuilder();
        for (String p : generateModelPath(d)) {
            sb.append(sb.length() == 0 ? '\'' : ':').append(p);
        }
        sb.append('\'');
        return sb.toString();
    }

    /** Outputs a function that returns the specified annotations, so that they can be loaded lazily.
     * @param annotations The annotations to be output.
     * @param d The declaration to which the annotations belong.
     * @param gen The generator to use for output. */
    static void outputAnnotationsFunction(final Tree.AnnotationList annotations, final Declaration d,
            final GenerateJsVisitor gen) {
        List<Tree.Annotation> anns = annotations == null ? null : annotations.getAnnotations();
        if (d != null) {
            int mask = MetamodelGenerator.encodeAnnotations(d, null);
            if (mask > 0) {
                gen.out(",", MetamodelGenerator.KEY_PACKED_ANNS, ":", Integer.toString(mask));
            }
            if (annotations == null || (anns.isEmpty() && annotations.getAnonymousAnnotation() == null)) {
                return;
            }
            anns = new ArrayList<>(annotations.getAnnotations().size());
            anns.addAll(annotations.getAnnotations());
            for (Iterator<Tree.Annotation> iter = anns.iterator(); iter.hasNext();) {
                final String qn = ((Tree.StaticMemberOrTypeExpression)iter.next().getPrimary()).getDeclaration().getQualifiedNameString();
                if (qn.startsWith("ceylon.language::") && MetamodelGenerator.annotationBits.contains(qn.substring(17))) {
                    iter.remove();
                }
            }
            if (anns.isEmpty() && annotations.getAnonymousAnnotation() == null) {
                return;
            }
            gen.out(",", MetamodelGenerator.KEY_ANNOTATIONS, ":");
        }
        if (annotations == null || (anns.isEmpty() && annotations.getAnonymousAnnotation()==null)) {
            gen.out("[]");
        } else {
            gen.out("function(){return[");
            boolean first = true;
            //Leave the annotation but remove the doc from runtime for brevity
            if (annotations.getAnonymousAnnotation() != null) {
                first = false;
                final Tree.StringLiteral lit = annotations.getAnonymousAnnotation().getStringLiteral();
                final String ptmd = pathToModelDoc(d);
                if (ptmd != null && ptmd.length() < lit.getText().length()) {
                    gen.out(gen.getClAlias(), "doc$($CCMM$,", ptmd);
                } else {
                    gen.out(gen.getClAlias(), "doc(");
                    lit.visit(gen);
                }
                gen.out(")");
            }
            for (Tree.Annotation a : anns) {
                if (first) first=false; else gen.out(",");
                gen.getInvoker().generateInvocation(a);
            }
            gen.out("];}");
        }
    }

    /** Abstraction for a callback that generates the runtime annotations list as part of the metamodel. */
    static interface RuntimeMetamodelAnnotationGenerator {
        public void generateAnnotations();
    }

    static class ModelAnnotationGenerator implements RuntimeMetamodelAnnotationGenerator {
        private final GenerateJsVisitor gen;
        private final Declaration d;
        private final Node node;
        ModelAnnotationGenerator(GenerateJsVisitor generator, Declaration decl, Node n) {
            gen = generator;
            d = decl;
            node = n;
        }
        @Override public void generateAnnotations() {
            List<Annotation> anns = d.getAnnotations();
            final int bits = MetamodelGenerator.encodeAnnotations(d, null);
            if (bits > 0) {
                gen.out(",", MetamodelGenerator.KEY_PACKED_ANNS, ":", Integer.toString(bits));
                //Remove these annotations from the list
                anns = new ArrayList<Annotation>(d.getAnnotations().size());
                anns.addAll(d.getAnnotations());
                for (Iterator<Annotation> iter = anns.iterator(); iter.hasNext();) {
                    final Annotation a = iter.next();
                    final Declaration ad = d.getUnit().getPackage().getMemberOrParameter(d.getUnit(), a.getName(), null, false);
                    final String qn = ad.getQualifiedNameString();
                    if (qn.startsWith("ceylon.language::") && MetamodelGenerator.annotationBits.contains(qn.substring(17))) {
                        iter.remove();
                    }
                }
                if (anns.isEmpty()) {
                    return;
                }
            }
            gen.out(",", MetamodelGenerator.KEY_ANNOTATIONS, ":function(){return[");
            boolean first = true;
            for (Annotation a : anns) {
                Declaration ad = d.getUnit().getPackage().getMemberOrParameter(d.getUnit(), a.getName(), null, false);
                if (ad instanceof Method) {
                    if (first) first=false; else gen.out(",");
                    final boolean isDoc = "ceylon.language::doc".equals(ad.getQualifiedNameString());
                    if (!isDoc) {
                        gen.qualify(node, ad);
                        gen.out(gen.getNames().name(ad), "(");
                    }
                    if (a.getPositionalArguments() == null) {
                        for (Parameter p : ((Method)ad).getParameterLists().get(0).getParameters()) {
                            String v = a.getNamedArguments().get(p.getName());
                            gen.out(v == null ? "undefined" : v);
                        }
                    } else {
                        if (isDoc) {
                            //Use ref if it's too long
                            final String ref = pathToModelDoc(d);
                            final String doc = a.getPositionalArguments().get(0);
                            if (ref != null && ref.length() < doc.length()) {
                                gen.out(gen.getClAlias(), "doc$($CCMM$,", ref);
                            } else {
                                gen.out(gen.getClAlias(), "doc(\"", gen.escapeStringLiteral(doc), "\"");
                            }
                        } else {
                            boolean farg = true;
                            for (String s : a.getPositionalArguments()) {
                                if (farg)farg=false; else gen.out(",");
                                gen.out("\"", gen.escapeStringLiteral(s), "\"");
                            }
                        }
                    }
                    gen.out(")");
                } else {
                    gen.out("/*MISSING DECLARATION FOR ANNOTATION ", a.getName(), "*/");
                }
            }
            gen.out("];}");
        }
    }

    /** Generates the right type arguments for operators that are sugar for method calls.
     * @param left The left term of the operator
     * @param right The right term of the operator
     * @param methodName The name of the method that is to be invoked
     * @param rightTpName The name of the type argument on the right term
     * @param leftTpName The name of the type parameter on the method
     * @return A map with the type parameter of the method as key
     * and the produced type belonging to the type argument of the term on the right. */
    static Map<TypeParameter, ProducedType> mapTypeArgument(final Tree.BinaryOperatorExpression expr,
            final String methodName, final String rightTpName, final String leftTpName) {
        Method md = (Method)expr.getLeftTerm().getTypeModel().getDeclaration().getMember(methodName, null, false);
        if (md == null) {
            expr.addUnexpectedError("Left term of intersection operator should have method named " + methodName);
            return null;
        }
        Map<TypeParameter, ProducedType> targs = expr.getRightTerm().getTypeModel().getTypeArguments();
        ProducedType otherType = null;
        for (TypeParameter tp : targs.keySet()) {
            if (tp.getName().equals(rightTpName)) {
                otherType = targs.get(tp);
                break;
            }
        }
        if (otherType == null) {
            expr.addUnexpectedError("Right term of intersection operator should have type parameter named " + rightTpName);
            return null;
        }
        targs = new HashMap<>();
        TypeParameter mtp = null;
        for (TypeParameter tp : md.getTypeParameters()) {
            if (tp.getName().equals(leftTpName)) {
                mtp = tp;
                break;
            }
        }
        if (mtp == null) {
            expr.addUnexpectedError("Left term of intersection should have type parameter named " + leftTpName);
        }
        targs.put(mtp, otherType);
        return targs;
    }

    /** Returns the qualified name of a declaration, skipping any containing methods. */
    public static String qualifiedNameSkippingMethods(Declaration d) {
        final StringBuilder p = new StringBuilder(d.getName());
        Scope s = d.getContainer();
        while (s != null) {
            if (s instanceof com.redhat.ceylon.compiler.typechecker.model.Package) {
                final String pkname = ((com.redhat.ceylon.compiler.typechecker.model.Package)s).getNameAsString();
                if (!pkname.isEmpty()) {
                    p.insert(0, "::");
                    p.insert(0, pkname);
                }
            } else if (s instanceof TypeDeclaration) {
                p.insert(0, '.');
                p.insert(0, ((TypeDeclaration)s).getName());
            }
            s = s.getContainer();
        }
        return p.toString();
    }

    public static String modelName(Declaration d) {
        if (d.isToplevel() || d.isShared()) {
            return d.getName();
        }
        if (d instanceof Setter) {
            d = ((Setter)d).getGetter();
        }
        return d.getName()+"$"+Long.toString(Math.abs((long)d.hashCode()), 36);
    }

}
TOP

Related Classes of com.redhat.ceylon.compiler.js.TypeUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.