compile.type.visit.SubstMap.java Source code

Java tutorial

Introduction

Here is the source code for compile.type.visit.SubstMap.java

Source

/**
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2009-2013 Adobe Systems Incorporated
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute
 * this file in accordance with the terms of the MIT license,
 * a copy of which can be found in the LICENSE.txt file or at
 * http://opensource.org/licenses/MIT.
 */
package compile.type.visit;

import com.google.common.collect.Sets;
import compile.Loc;
import compile.Pair;
import compile.Session;
import compile.StringUtils;
import compile.type.Type;
import compile.type.TypeEnv;
import compile.type.TypeVar;
import compile.type.constraint.Constraint;

import java.util.*;

/**
 * Type substitution map.
 *
 * @author Basil Hosmer
 */
@SuppressWarnings("serial")
public final class SubstMap extends LinkedHashMap<Type, Type> {
    /**
     * empty substitution map, must not be modified
     */
    public static SubstMap EMPTY = new SubstMap();

    /**
     * return a substitution map binding a var to a type.
     */
    public static SubstMap bindVar(final Loc loc, final TypeVar var, final Type type, final TypeEnv env) {
        if (var.equals(type))
            return EMPTY;

        if (!var.getKind().equals(type.getKind())) {
            Session.error(loc, "attempting to bind type variable {0} with kind {1} to type {2} with kind {3}",
                    var.dump(), var.getKind().dump(), type.dump(), type.getKind().dump());

            return null;
        }

        if (type.getVars().contains(var)) {
            Session.error(loc, "type variable {0} occurs in type {1} - recursive types are not supported",
                    var.dump(), type.dump());

            return null;
        }

        if (type instanceof TypeVar) {
            // TODO this case should happen as a natural result of calling
            // TODO var.constraint.satisfy with a TypeVar argument

            // for var -> var, we need to check and possibly manipulate constraints
            final TypeVar rhsVar = (TypeVar) type;

            final Pair<? extends Constraint, SubstMap> merged = var.getConstraint().merge(rhsVar.getConstraint(),
                    env);

            if (merged == null) {
                Session.error(loc, "type constraints {0} and {1} are incompatible", var.getConstraint().dump(),
                        rhsVar.getConstraint().dump());

                return null;
            }

            final Constraint mergedConstraint = merged.left;
            final SubstMap mergeSubst = merged.right;

            if (mergedConstraint != rhsVar.getConstraint())
                rhsVar.setConstraint(mergedConstraint.subst(mergeSubst));

            // if lhs var has param info, transfer it.
            // this doesn't affect inference, but helps preserves declared param names.
            if (var.hasSourceParam())
                rhsVar.addUnifiedParam(var.getSourceParam());

            rhsVar.addUnifiedParams(var.getUnifiedParams());

            return mergeSubst.compose(loc, new SubstMap(var, rhsVar));
        } else {
            final SubstMap sat = var.getConstraint().satisfy(loc, type, env);

            if (sat == null) {
                // TODO need to get this back out to TypeChecker to error-format types
                Session.error(loc, "type {0} does not satisfy constraint {1}", type.dump(),
                        var.getConstraint().dump());

                return null;
            }

            return sat.compose(loc, new SubstMap(var, type));
        }
    }

    /**
     * Check for agreement with another map. If maps m1 and m2 agree, there is no
     * LHS v for which m1.contains(v) && m2.contains(v) && m1.get(v) != m2.get(v).
     * An error is raised for each LHS on which maps disagree.
     */
    public static boolean checkAgreement(final Loc loc, final SubstMap left, final SubstMap right) {
        boolean agrees = true;

        for (final Type type : Sets.intersection(left.keySet(), right.keySet())) {
            final Type leftType = left.get(type);
            final Type rightType = right.get(type);

            if (!leftType.equals(rightType)) {
                // TODO always internal? if not, change message
                Session.error(loc, "internal error: substitution maps disagree: {0} vs. {1}", leftType.dump(),
                        rightType.dump());
                agrees = false;
            }
        }

        return agrees;
    }

    //
    // instance
    //

    /**
     * Empty map
     */
    public SubstMap() {
    }

    /**
     * map with a single binding
     */
    public SubstMap(final TypeVar lhs, final Type rhs) {
        put(lhs, rhs);
    }

    /**
     * Compose our substitutions with "newer" substitutions from another map.
     * <p/>
     * Note: "newer" means that RHSs of new substs cannot mention vars that are LHSs
     * of old substs. If they do, the resulting map may have cycles, and a call to
     * type.apply(substMap) isn't guaranteed to return a complete substitution.
     * In this case one or more errors are raised.
     */
    public SubstMap compose(final Loc loc, final SubstMap newSubs) {
        if (isEmpty())
            return newSubs;

        if (newSubs.isEmpty())
            return this;

        final SubstMap result = new SubstMap();

        for (final Map.Entry<Type, Type> entry : entrySet()) {
            final Type var = entry.getKey();
            final Type type = entry.getValue();
            result.put(var, type.subst(newSubs));
        }

        result.putAll(newSubs);
        result.checkClosure(loc, Sets.newHashSet(newSubs.values()));

        return result;
    }

    /**
     * Check map for closure: no types in check set can mention any LHS vars.
     *
     */
    public boolean checkClosure(final Loc loc, final Set<Type> newRhses) {
        final Set<Type> lhsVars = keySet();

        final Set<Type> cycles = Sets.newLinkedHashSet();

        for (final Type rhs : newRhses) {
            for (final TypeVar rhsVar : rhs.getVars()) {
                if (lhsVars.contains(rhsVar)) {
                    cycles.add(rhs);

                    Session.error(loc,
                            "internal error: substitution map is not closed. RHS type {0} mentions LHS var {1}, map = {2}",
                            rhs.dump(), rhsVar.dump(), dump());
                }
            }
        }

        if (!cycles.isEmpty()) {
            return false;
        }

        return true;
    }

    /**
     * Dump substitutions to string
     */
    public String dump() {
        final Map<Type, List<Type>> rmap = new LinkedHashMap<Type, List<Type>>();

        for (final Map.Entry<Type, Type> entry : entrySet()) {
            final Type key = entry.getKey();
            final Type value = entry.getValue();

            if (!rmap.containsKey(value))
                rmap.put(value, new ArrayList<Type>());

            rmap.get(value).add(key);
        }

        final List<String> dumps = new ArrayList<String>();

        for (final Map.Entry<Type, List<Type>> entry : rmap.entrySet())
            dumps.add(TypeDumper.dumpList(entry.getValue(), " ") + " => " + entry.getKey().dump());

        return "[" + StringUtils.join(dumps, ", ") + "]";
    }
}