// ex: set sts=4 sw=4 expandtab: /** * Yeti type analyzer. * Uses Hindley-Milner type inference algorithm * with extensions for polymorphic structs and variants. * Copyright (c) 2007-2013 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.IdentityHashMap; import java.util.Iterator; class YType { int type; Map requiredMembers; Map allowedMembers; YType[] param; YType ref; int depth; int flags; int field; boolean seen; Object doc; JavaType javaType; YType(int depth) { this.depth = depth; } YType(int type, YType[] param) { this.type = type; this.param = param; } YType(String javaSig) { type = YetiType.JAVA; this.javaType = JavaType.fromDescription(javaSig); param = YetiType.NO_PARAM; } public String toString() { return (String) new ShowTypeFun().apply("", TypeDescr.yetiType(this, null, null)); } public String toString(Scope scope, TypeException ex) { return (String) new ShowTypeFun().apply("", TypeDescr.yetiType(this, TypePattern.toPattern(scope, false), ex)); } YType deref() { YType res = this; while (res.ref != null) { res = res.ref; } for (YType next, type = this; type.ref != null; type = next) { next = type.ref; type.ref = res; } if ((res.type <= 0 || res.type > YetiType.PRIMITIVE_END) && res.doc == null) res.doc = this; return res; } String doc() { for (YType t = this; t != null; t = t.ref) if (t.doc != null) { String doc; if (t.doc instanceof YType) { YType ref = (YType) t.doc; t.doc = null; doc = ref.doc(); } else { doc = (String) t.doc; } if (doc != null) { if ((doc = doc.trim()).length() != 0) { t.doc = doc; return doc; } t.doc = null; } } return null; } } class TypeException extends Exception { boolean special; YType a, b; String sep, ext; List trace; TypeException(String what) { super(what); trace = new ArrayList(); } TypeException(YType a_, YType b_) { a = a_; b = b_; sep = " is not "; ext = ""; trace = new ArrayList(); } TypeException(YType a_, String sep_, YType b_, String ext_) { a = a_; b = b_; sep = sep_; ext = ext_; trace = new ArrayList(); } public String getMessage() { return getMessage(null); } public String getMessage(Scope scope) { if (a == null) return super.getMessage(); return "Type mismatch: " + a.toString(scope, null) + sep + b.toString(scope, null) + ext; } } class Scope { Scope outer; String name; Binder binder; YType[] free; Closure closure; // non-null means outer scopes must be proxied YetiType.ClassBinding importClass; YetiType.ScopeCtx ctx; Scope(Scope outer, String name, Binder binder) { this.outer = outer; this.name = name; this.binder = binder; ctx = outer == null ? null : outer.ctx; } YType[] typedef(boolean use) { return null; } } final class TypeScope extends Scope { private final YType[] def; final LoadModule module; TypeScope(Scope outer, String name, YType[] typedef, LoadModule m) { super(outer, name, null); def = typedef; free = YetiType.NO_PARAM; module = m; } YType[] typedef(boolean use) { if (use && module != null) module.typedefUsed = true; return def; } } class YetiType implements YetiParser { static final int VAR = 0; static final int UNIT = 1; static final int STR = 2; static final int NUM = 3; static final int BOOL = 4; static final int CHAR = 5; static final int NONE = 6; static final int LIST_MARKER = 7; static final int MAP_MARKER = 8; static final int PRIMITIVE_END = 8; static final int FUN = 9; // a -> b static final int MAP = 10; // value, index, (LIST | MAP) static final int STRUCT = 11; static final int VARIANT = 12; static final int JAVA = 13; static final int JAVA_ARRAY = 14; static final int OPAQUE_TYPES = 0x10000; static final int FL_ORDERED_REQUIRED = 1; static final int FL_TAINTED_VAR = 2; static final int FL_AMBIGUOUS_OPAQUE = 4; static final int FL_ANY_CASE = 8; static final int FL_FLEX_TYPEDEF = 0x10; static final int FL_ERROR_IS_HERE = 0x100; static final int FL_ANY_PATTERN = 0x4000; static final int FL_PARTIAL_PATTERN = 0x8000; static final int FIELD_NON_POLYMORPHIC = 1; static final int FIELD_MUTABLE = 2; static final YType[] NO_PARAM = {}; static final YType UNIT_TYPE = new YType(UNIT, NO_PARAM); static final YType NUM_TYPE = new YType(NUM, NO_PARAM); static final YType STR_TYPE = new YType(STR, NO_PARAM); static final YType BOOL_TYPE = new YType(BOOL, NO_PARAM); static final YType CHAR_TYPE = new YType(CHAR, NO_PARAM); static final YType NO_TYPE = new YType(NONE, NO_PARAM); static final YType LIST_TYPE = new YType(LIST_MARKER, NO_PARAM); static final YType MAP_TYPE = new YType(MAP_MARKER, NO_PARAM); static final YType ORDERED = orderedVar(1); static final YType A = new YType(1); static final YType B = new YType(1); static final YType C = new YType(1); static final YType EQ_TYPE = fun2Arg(A, A, BOOL_TYPE); static final YType LG_TYPE = fun2Arg(ORDERED, ORDERED, BOOL_TYPE); static final YType NUMOP_TYPE = fun2Arg(NUM_TYPE, NUM_TYPE, NUM_TYPE); static final YType BOOLOP_TYPE = fun2Arg(BOOL_TYPE, BOOL_TYPE, BOOL_TYPE); static final YType A_B_LIST_TYPE = new YType(MAP, new YType[] { A, B, LIST_TYPE }); static final YType NUM_LIST_TYPE = new YType(MAP, new YType[] { NUM_TYPE, B, LIST_TYPE }); static final YType A_B_MAP_TYPE = new YType(MAP, new YType[] { B, A, MAP_TYPE }); static final YType A_B_C_MAP_TYPE = new YType(MAP, new YType[] { B, A, C }); static final YType A_LIST_TYPE = new YType(MAP, new YType[] { A, NO_TYPE, LIST_TYPE }); static final YType C_LIST_TYPE = new YType(MAP, new YType[] { C, NO_TYPE, LIST_TYPE }); static final YType STRING_ARRAY = new YType(MAP, new YType[] { STR_TYPE, NUM_TYPE, LIST_TYPE }); static final YType CONS_TYPE = fun2Arg(A, A_B_LIST_TYPE, A_LIST_TYPE); static final YType LAZYCONS_TYPE = fun2Arg(A, fun(UNIT_TYPE, A_B_LIST_TYPE), A_LIST_TYPE); static final YType A_TO_BOOL = fun(A, BOOL_TYPE); static final YType LIST_TO_A = fun(A_B_LIST_TYPE, A); static final YType MAP_TO_BOOL = fun(A_B_C_MAP_TYPE, BOOL_TYPE); static final YType MAP_TO_NUM = fun(A_B_C_MAP_TYPE, NUM_TYPE); static final YType LIST_TO_LIST = fun(A_B_LIST_TYPE, A_LIST_TYPE); static final YType IN_TYPE = fun2Arg(A, A_B_C_MAP_TYPE, BOOL_TYPE); static final YType COMPOSE_TYPE = fun2Arg(fun(B, C), fun(A, B), fun(A, C)); static final YType BOOL_TO_BOOL = fun(BOOL_TYPE, BOOL_TYPE); static final YType NUM_TO_NUM = fun(NUM_TYPE, NUM_TYPE); static final YType STR_TO_NUM_TO_STR = fun2Arg(STR_TYPE, NUM_TYPE, STR_TYPE); static final YType FOR_TYPE = fun2Arg(A_B_LIST_TYPE, fun(A, UNIT_TYPE), UNIT_TYPE); static final YType STR2_PRED_TYPE = fun2Arg(STR_TYPE, STR_TYPE, BOOL_TYPE); static final YType SYNCHRONIZED_TYPE = fun2Arg(A, fun(UNIT_TYPE, B), B); static final YType CLASS_TYPE = new YType("Ljava/lang/Class;"); static final YType OBJECT_TYPE = new YType("Ljava/lang/Object;"); static final YType WITH_EXIT_TYPE = fun(fun(fun(A, B), A), A); static final YType THROW_TYPE = fun(new YType("Ljava/lang/Throwable;"), A); static final YType[] PRIMITIVES = { null, UNIT_TYPE, STR_TYPE, NUM_TYPE, BOOL_TYPE, CHAR_TYPE, NO_TYPE, LIST_TYPE, MAP_TYPE }; static final String[] TYPE_NAMES = { "var", "()", "string", "number", "boolean", "char", "none", "list", "hash", "fun", "list", "struct", "variant", "object" }; static final Scope ROOT_SCOPE = bindCompare("==", EQ_TYPE, CompareFun.COND_EQ, // equals is 0 for false bindCompare("!=", EQ_TYPE, CompareFun.COND_NOT, // equals is 0 for false bindCompare("<" , LG_TYPE, CompareFun.COND_LT, bindCompare("<=", LG_TYPE, CompareFun.COND_LE, bindCompare(">" , LG_TYPE, CompareFun.COND_GT, bindCompare(">=", LG_TYPE, CompareFun.COND_GE, bindPoly(".", COMPOSE_TYPE, new BuiltIn(6), bindPoly("in", IN_TYPE, new BuiltIn(2), bindPoly("::", CONS_TYPE, new BuiltIn(3), bindPoly(":.", LAZYCONS_TYPE, new BuiltIn(4), bindPoly("for", FOR_TYPE, new BuiltIn(5), bindPoly("nullptr?", A_TO_BOOL, new BuiltIn(8), bindPoly("defined?", A_TO_BOOL, new BuiltIn(9), bindPoly("empty?", MAP_TO_BOOL, new BuiltIn(10), bindPoly("same?", EQ_TYPE, new BuiltIn(21), bindPoly("head", LIST_TO_A, new BuiltIn(11), bindPoly("tail", LIST_TO_LIST, new BuiltIn(12), bindPoly("synchronized", SYNCHRONIZED_TYPE, new BuiltIn(7), bindPoly("withExit", WITH_EXIT_TYPE, new BuiltIn(24), bindPoly("length", MAP_TO_NUM, new BuiltIn(25), bindPoly("throw", WITH_EXIT_TYPE, new BuiltIn(26), bindArith("+", "add", bindArith("-", "sub", bindArith("*", "mul", bindArith("/", "div", bindArith("%", "rem", bindArith("div", "intDiv", bindArith("shl", "shl", bindArith("shr", "shr", bindArith("b_and", "and", bindArith("b_or", "or", bindArith("xor", "xor", bindScope("=~", new BuiltIn(13), bindScope("!~", new BuiltIn(14), bindScope("not", new BuiltIn(15), bindScope("and", new BoolOpFun(false), bindScope("or", new BoolOpFun(true), bindScope("undef_bool", new BuiltIn(17), bindScope("false", new BuiltIn(18), bindScope("true", new BuiltIn(19), bindScope("negate", new BuiltIn(20), bindScope("undef_str", new BuiltIn(23), bindScope("strChar", new BuiltIn(16), bindStr("strLength", fun(STR_TYPE, NUM_TYPE), "length", "()I", bindStr("strUpper", fun(STR_TYPE, STR_TYPE), "toUpperCase", "()Ljava/lang/String;", bindStr("strLower", fun(STR_TYPE, STR_TYPE), "toLowerCase", "()Ljava/lang/String;", bindStr("strTrim", fun(STR_TYPE, STR_TYPE), "trim", "()Ljava/lang/String;", bindStr("strSlice", fun2Arg(STR_TYPE, NUM_TYPE, fun(NUM_TYPE, STR_TYPE)), "substring", "(II)Ljava/lang/String;", bindStr("strRight", fun2Arg(STR_TYPE, NUM_TYPE, STR_TYPE), "substring", "(I)Ljava/lang/String;", bindStr("strStarts?", fun2Arg(STR_TYPE, STR_TYPE, BOOL_TYPE), "startsWith", "(Ljava/lang/String;)Z", bindStr("strEnds?", fun2Arg(STR_TYPE, STR_TYPE, BOOL_TYPE), "endsWith", "(Ljava/lang/String;)Z", bindStr("strIndexOf", fun2Arg(STR_TYPE, STR_TYPE, fun(NUM_TYPE, NUM_TYPE)), "indexOf", "(Ljava/lang/String;I)I", bindStr("strLastIndexOf", fun2Arg(STR_TYPE, STR_TYPE, fun(NUM_TYPE, NUM_TYPE)), "lastIndexOf", "(Ljava/lang/String;I)I", bindStr("strLastIndexOf'", fun2Arg(STR_TYPE, STR_TYPE, NUM_TYPE), "lastIndexOf", "(Ljava/lang/String;)I", bindRegex("strSplit", "yeti/lang/StrSplit", fun2Arg(STR_TYPE, STR_TYPE, STRING_ARRAY), bindRegex("like", "yeti/lang/Like", fun2Arg(STR_TYPE, STR_TYPE, fun(UNIT_TYPE, STRING_ARRAY)), bindRegex("substAll", "yeti/lang/SubstAll", fun2Arg(STR_TYPE, STR_TYPE, fun(STR_TYPE, STR_TYPE)), bindRegex("matchAll", "yeti/lang/MatchAll", fun2Arg(STR_TYPE, fun(STRING_ARRAY, A), fun2Arg(fun(STR_TYPE, A), STR_TYPE, A_LIST_TYPE)), bindImport("EmptyArray", "yeti/lang/EmptyArrayException", bindImport("Failure", "yeti/lang/FailureException", bindImport("NoSuchKey", "yeti/lang/NoSuchKeyException", bindImport("Exception", "java/lang/Exception", bindImport("RuntimeException", "java/lang/RuntimeException", bindImport("NumberFormatException", "java/lang/NumberFormatException", bindImport("IllegalArgumentException", "java/lang/IllegalArgumentException", bindImport("Math", "java/lang/Math", bindImport("Object", "java/lang/Object", bindImport("Boolean", "java/lang/Boolean", bindImport("Integer", "java/lang/Integer", bindImport("Long", "java/lang/Long", bindImport("Double", "java/lang/Double", bindImport("String", "java/lang/String", null)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))); static final Scope ROOT_SCOPE_SYS = bindImport("System", "java/lang/System", bindImport("Class", "java/lang/Class", ROOT_SCOPE)); static final JavaType COMPARABLE = JavaType.fromDescription("Ljava/lang/Comparable;"); static Scope bindScope(String name, Binder binder, Scope scope) { return new Scope(scope, name, binder); } static Scope bindCompare(String op, YType type, int code, Scope scope) { return bindPoly(op, type, new Compare(type, code, op), scope); } static Scope bindArith(String op, String method, Scope scope) { return bindScope(op, new ArithOp(op, method, NUMOP_TYPE), scope); } static Scope bindStr(String name, YType type, String method, String sig, Scope scope) { return bindScope(name, new StrOp(name, method, sig, type), scope); } static Scope bindRegex(String name, String impl, YType type, Scope scope) { return bindPoly(name, type, new Regex(name, impl, type), scope); } static Scope bindImport(String name, String className, Scope scope) { scope = new Scope(scope, name, null); scope.importClass = new ClassBinding(new YType('L' + className + ';')); return scope; } static YType fun(YType a, YType res) { return new YType(FUN, new YType[] { a, res }); } static YType fun2Arg(YType a, YType b, YType res) { return new YType(FUN, new YType[] { a, new YType(FUN, new YType[] { b, res }) }); } static YType mutableFieldRef(YType src) { YType t = new YType(src.depth); t.ref = src.ref; t.flags = src.flags; t.field = FIELD_MUTABLE; return t; } static YType fieldRef(int depth, YType ref, int kind) { YType t = new YType(depth); t.ref = ref.deref(); t.field = kind; t.doc = ref.doc; return t; } static class ClassBinding { final YType type; ClassBinding(YType classType) { this.type = classType; } BindRef[] getCaptures() { return null; } // proxies - List<Closure> ClassBinding dup(List proxies) { return this; } } static final class ScopeCtx { final String packageName; final String className; final Map opaqueTypes; final Compiler compiler; ScopeCtx(String className_, Compiler compiler_) { packageName = JavaType.packageOfClass(className_); className = className_; opaqueTypes = compiler_.opaqueTypes; compiler = compiler_; } } static YType orderedVar(int maxDepth) { YType type = new YType(maxDepth); type.flags = FL_ORDERED_REQUIRED; return type; } static void limitDepth(YType type, int maxDepth, int setFlag) { type = type.deref(); if (type.type != VAR) { if (type.seen) return; type.seen = true; for (int i = type.param.length; --i >= 0;) limitDepth(type.param[i], maxDepth, setFlag); type.seen = false; } else { if (type.depth > maxDepth) type.depth = maxDepth; type.flags |= setFlag; } } static void mismatch(YType a, YType b) throws TypeException { throw new TypeException(a, b); } static void finalizeStruct(YType partial, YType src) throws TypeException { if (src.allowedMembers == null || partial.requiredMembers == null /*|| partial.allowedMembers != null*/) { return; // nothing to check } Object[] members = partial.requiredMembers.entrySet().toArray(); Object current = null; try { for (int i = 0; i < members.length; ++i) { Map.Entry entry = (Map.Entry) members[i]; Object name = entry.getKey(); YType ff = (YType) src.allowedMembers.get(name); if (ff == null) { if ((partial.flags & FL_ANY_CASE) != 0) continue; throw new TypeException(src, " => ", partial, " (member missing: " + name + ")"); } YType partField = (YType) entry.getValue(); if (partField.field == FIELD_MUTABLE && ff.field != FIELD_MUTABLE) throw new TypeException("Field '" + name + "' constness mismatch: " + src + " => " + partial); current = name; unify(partField, ff); current = null; } } catch (TypeException ex) { if (current != null) { ex.trace.add(current); ex.trace.add(partial); ex.trace.add(src); } throw ex; } } static void unifyMembers(YType a, YType b) throws TypeException { YType oldRef = b.ref; Object currentField = null; try { b.ref = a; // just fake ref now to avoid cycles... Map ff; if (((a.flags | b.flags) & FL_FLEX_TYPEDEF) != 0) { int x = (a.allowedMembers != null ? 1 : 0) | (a.requiredMembers != null ? 2 : 0) | (b.allowedMembers != null ? 4 : 0) | (b.requiredMembers != null ? 8 : 0); if (x == 6 || x == 9) { // 0110 or 1001 YType t = (a.flags & FL_FLEX_TYPEDEF) != 0 ? a : b; Map tmp = t.allowedMembers; t.allowedMembers = t.requiredMembers; t.requiredMembers = tmp; t.flags &= ~FL_FLEX_TYPEDEF; // flip only once. } } // don't be smart anymore a.flags &= ~FL_FLEX_TYPEDEF; if (((a.flags ^ b.flags) & FL_ORDERED_REQUIRED) != 0) { // VARIANT types are sometimes ordered. // when all their variant parameters are ordered types. if ((a.flags & FL_ORDERED_REQUIRED) != 0) requireOrdered(b); else requireOrdered(a); } if (a.allowedMembers == null) { ff = b.allowedMembers; } else if (b.allowedMembers == null) { ff = a.allowedMembers; } else { // unify final members ff = new IdentityHashMap(a.allowedMembers); for (Iterator i = ff.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); currentField = entry.getKey(); YType f = (YType) b.allowedMembers.get(currentField); if (f != null) { YType t = (YType) entry.getValue(); unify(f, t); // constness spreads if (t.field != f.field) { if (t.field == 0) entry.setValue(t = f); t.field = FIELD_NON_POLYMORPHIC; } } else { i.remove(); } } currentField = null; if (ff.isEmpty()) mismatch(a, b); } finalizeStruct(a, b); finalizeStruct(b, a); if (ff != null && (b.flags & FL_ANY_CASE) != 0) { if ((a.flags & FL_ANY_CASE) != 0) a.requiredMembers = null; } else if (a.requiredMembers == null || (ff != null && (a.flags & FL_ANY_CASE) != 0)) { a.requiredMembers = b.requiredMembers; } else if (b.requiredMembers != null) { // join partial members Object[] aa = a.requiredMembers.entrySet().toArray(); for (int i = 0; i < aa.length; ++i) { Map.Entry entry = (Map.Entry) aa[i]; currentField = entry.getKey(); YType f = (YType) b.requiredMembers.get(currentField); if (f != null) { unify((YType) entry.getValue(), f); // mutability spreads if (f.field >= FIELD_NON_POLYMORPHIC) entry.setValue(f); } } currentField = null; a.requiredMembers.putAll(b.requiredMembers); } a.allowedMembers = ff; a.flags &= b.flags | ~(FL_ANY_CASE | FL_FLEX_TYPEDEF); if (ff == null) { ff = a.requiredMembers; } else if (a.requiredMembers != null) { ff = new IdentityHashMap(ff); ff.putAll(a.requiredMembers); } unify(a.param[0], b.param[0]); structParam(a, ff, a.param[0].deref()); b.type = VAR; b.ref = a; if ((a.param[0].flags & FL_TAINTED_VAR) != 0) limitDepth(a, a.param[0].depth, FL_TAINTED_VAR); } catch (TypeException ex) { b.ref = oldRef; if (currentField != null) { ex.trace.add(currentField); ex.trace.add(a); ex.trace.add(b); } throw ex; } } static void structParam(YType st, Map values, YType depth) { if (depth.type != VAR || depth.ref != null) throw new IllegalStateException("non-freevar struct depth: " + depth); YType[] a = new YType[values.size() + 1]; a[0] = depth; Iterator i = values.values().iterator(); for (int j = 1; i.hasNext(); ++j) a[j] = (YType) i.next(); st.param = a; } static void unifyJava(YType jt, YType t) throws TypeException { String descr = jt.javaType.description; if (t.type != JAVA) { if (t.type == UNIT && descr == "V") return; mismatch(jt, t); } if (descr == t.javaType.description) return; mismatch(jt, t); } static void requireOrdered(YType type) throws TypeException { switch (type.type) { case VARIANT: if ((type.flags & FL_ORDERED_REQUIRED) == 0) { if (type.requiredMembers != null) { Iterator i = type.requiredMembers.values().iterator(); while (i.hasNext()) requireOrdered((YType) i.next()); } if (type.allowedMembers != null) { Iterator i = type.allowedMembers.values().iterator(); while (i.hasNext()) requireOrdered((YType) i.next()); type.flags |= FL_ORDERED_REQUIRED; } } return; case MAP: requireOrdered(type.param[2]); requireOrdered(type.param[0]); return; case VAR: if (type.ref != null) requireOrdered(type.ref); else type.flags |= FL_ORDERED_REQUIRED; case NUM: case STR: case LIST_MARKER: return; case JAVA: try { if (COMPARABLE.isAssignable(type.javaType) >= 0) return; } catch (JavaClassNotFoundException ex) { throw new TypeException("Unknown class: " + ex.getMessage()); } } TypeException ex = new TypeException(type + " is not an ordered type"); ex.special = true; throw ex; } static void occursCheck(YType type, YType var) throws TypeException { type = type.deref(); if (type == var) { TypeException ex = new TypeException("Cyclic types are not allowed"); ex.special = true; throw ex; } if (type.param != null && type.type != VARIANT && type.type != STRUCT) for (int i = type.param.length; --i >= 0;) occursCheck(type.param[i], var); } static void unifyToVar(YType var, YType from) throws TypeException { occursCheck(from, var); if ((var.flags & FL_ORDERED_REQUIRED) != 0) requireOrdered(from); limitDepth(from, var.depth, var.flags & FL_TAINTED_VAR); var.ref = from; } static void unify(YType a, YType b) throws TypeException { a = a.deref(); b = b.deref(); if (a == b) return; if (a.type == VAR) { unifyToVar(a, b); return; } if (b.type == VAR) { unifyToVar(b, a); return; } if (a.type != b.type) { YType opaque = null; if (a.type >= OPAQUE_TYPES && (a.flags & FL_AMBIGUOUS_OPAQUE) != 0) opaque = a; else if (b.type >= OPAQUE_TYPES && (b.flags & FL_AMBIGUOUS_OPAQUE) != 0) opaque = b; if (opaque != null) { opaque.ref = (YType) opaque.allowedMembers.values().toArray()[0]; opaque.type = 0; unify(a, b); return; } } if (a.type == JAVA) { unifyJava(a, b); } else if (b.type == JAVA) { unifyJava(b, a); } else if (a.type != b.type) { mismatch(a, b); } else if (a.type == STRUCT || a.type == VARIANT) { unifyMembers(a, b); } else if (a.type == MAP && (a.param[1].type ^ b.param[1].type) == (NUM ^ NONE) && (a.param[1].type == NONE || b.param[1].type == NONE)) { mismatch(a, b); } else { for (int i = 0, cnt = a.param.length; i < cnt; ++i) unify(a.param[i], b.param[i]); if (a.type >= OPAQUE_TYPES && (a.flags & b.flags & FL_AMBIGUOUS_OPAQUE) == 0) { a.flags &= ~FL_AMBIGUOUS_OPAQUE; b.flags &= ~FL_AMBIGUOUS_OPAQUE; } } } static void unify(YType a, YType b, Node where, Scope scope, YType param1, YType param2, String error) { try { unify(a, b); } catch (TypeException ex) { throw new CompileException(where, scope, param1, param2, error, ex); } } static void unify(YType a, YType b, Node where, Scope scope, String error) { unify(a, b, where, scope, a, b, error); } static YType mergeOrUnify(YType to, YType val) throws TypeException { YType t = JavaType.mergeTypes(to, val); if (t != null) return t; unify(to, val); return to; } static Map copyTypeMap(Map types, Map free, Map known) { Map result = new IdentityHashMap(types.size()); for (Iterator i = types.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); YType t = (YType) entry.getValue(); YType nt = copyType(t, free, known); // looks like a hack, but fixing here avoids unnecessery refs if (t.field != nt.field) { if (t.field != 0) { YType tmp = new YType(0); tmp.ref = nt; nt = tmp; } nt.field = t.field; nt.flags = t.flags; } result.put(entry.getKey(), nt); } return result; } // free should be given null only in that special case, // when it is desirable to copy non-polymorphic structures. // Only known such case currently is in the opaqueCast function. static YType copyType(YType type_, Map free, Map known) { YType res, type = type_.deref(); if (type.type == VAR) return free != null && (res = (YType) free.get(type)) != null ? res : type; if (type.param.length == 0 && type.type < OPAQUE_TYPES) return type_; YType copy = (YType) known.get(type); if (copy != null) return copy; /* No structure without polymorphic flag variable shouldn't be copied. * The getFreeVar should ensure that any variable reachable through * such structure isn't free either. */ if ((type.type == STRUCT || type.type == VARIANT) && free != null && !free.containsKey(type.param[0])) { return type; } YType[] param = new YType[type.param.length]; copy = new YType(type.type, param); copy.doc = type_; res = copy; if (type_.field >= FIELD_NON_POLYMORPHIC) { res = mutableFieldRef(type_); res.field = type_.field; res.ref = copy; } known.put(type, res); for (int i = param.length; --i >= 0;) param[i] = copyType(type.param[i], free, known); if (type.requiredMembers != null) { copy.flags = type.flags & (FL_ANY_CASE | FL_FLEX_TYPEDEF); copy.requiredMembers = copyTypeMap(type.requiredMembers, free, known); } if (type.allowedMembers != null) { copy.flags |= type.flags & FL_FLEX_TYPEDEF; copy.allowedMembers = copyTypeMap(type.allowedMembers, free, known); } return res; } static Map createFreeVars(YType[] freeTypes, int depth) { IdentityHashMap vars = new IdentityHashMap(freeTypes.length); for (int i = freeTypes.length; --i >= 0;) { YType free = freeTypes[i]; YType t = new YType(depth); t.flags = free.flags; t.field = free.field; vars.put(free, t); } return vars; } private static BindRef resolveRef(String sym, Node where, Scope scope, Scope[] r) { for (; scope != null; scope = scope.outer) { if (scope.name == sym && scope.binder != null) { r[0] = scope; return scope.binder.getRef(where.line); } if (scope.closure != null) return scope.closure.refProxy( resolveRef(sym, where, scope.outer, r)); } throw new CompileException(where, "Unknown identifier: " + sym); } static BindRef resolve(String sym, Node where, Scope scope, int depth) { Scope[] r = new Scope[1]; BindRef ref = resolveRef(sym, where, scope, r); // We have to copy even polymorph refs with NO free variables, // because the goddamn structs are wicked with their // provided/requested member lists. if (r[0].free != null && (ref.polymorph || r[0].free.length != 0)) { ref = ref.unshare(); Map vars = createFreeVars(r[0].free, depth + 1); ref.type = copyType(ref.type, vars, new IdentityHashMap()); } return ref; } static YType resolveClass(String name, Scope scope, boolean shadow) { if (name.indexOf('/') >= 0) return JavaType.typeOfClass(null, name); for (; scope != null; scope = scope.outer) if (scope.name == name) { if (scope.importClass != null) return scope.importClass.type; if (shadow) break; } return null; } static ClassBinding resolveFullClass(String name, Scope scope, boolean refs, Node checkPerm) { String packageName = scope.ctx.packageName; YType t; if (name.indexOf('/') >= 0) { packageName = null; } else if (refs) { List proxies = new ArrayList(); for (Scope s = scope; s != null; s = s.outer) { if (s.name == name && s.importClass != null) return s.importClass.dup(proxies); if (s.closure != null) proxies.add(s.closure); } } else if ((t = resolveClass(name, scope, false)) != null) { return new ClassBinding(t); } if (checkPerm != null && (scope.ctx.compiler.globalFlags & Compiler.GF_NO_IMPORT) != 0) throw new CompileException(checkPerm, name + " is not imported"); return new ClassBinding(JavaType.typeOfClass(packageName, name)); } static YType resolveFullClass(String name, Scope scope) { YType t = resolveClass(name, scope, false); return t == null ? JavaType.typeOfClass(scope.ctx.packageName, name) : t; } static boolean hasMutableStore(Map bindVars, YType result, boolean store) { if (!result.seen) { if (result.field >= FIELD_NON_POLYMORPHIC) store = true; YType t = result.deref(); if (t.type == VAR) return store && bindVars.containsKey(t); if (t.type == MAP && t.param[1] != NO_TYPE) store = true; result.seen = true; for (int i = t.param.length; --i >= 0;) if (hasMutableStore(bindVars, t.param[i], store || i == 0 && t.type == FUN)) { result.seen = false; return true; } result.seen = false; } return false; } // difference from getFreeVar is that functions don't protect static void restrictArg(YType type, int depth, boolean active) { if (type.seen) return; if (type.field >= FIELD_NON_POLYMORPHIC) active = true; // anything under mutable field is evil YType t = type.deref(), k; int tt = t.type; if (tt != VAR) { type.seen = true; for (int i = t.param.length; --i >= 0;) { if (i == 1 && !active) active = tt == MAP && (k = t.param[1].deref()) != NO_TYPE && (k.type != VAR || t.param[2] != LIST_TYPE); // array/hash value is in mutable store and evil restrictArg(t.param[i], depth, active); } type.seen = false; } else if (active && t.depth >= depth) { t.flags |= FL_TAINTED_VAR; } } private static final int RESTRICT_PROTECT = 1; private static final int RESTRICT_CONTRA = 2; static final int RESTRICT_ALL = 4; static final int RESTRICT_POLY = 8; static final int STRUCT_VAR = 16; /* * All free vars reachable through structures that have flag var denied * must be denied. This is n:m relationship - structure can have many free * vars reachable through it, and a free var can be reachable through many * structures. Since suppressed var can be another structures flag var, * circular dependencies can arise, therefore it can't be done in single * step. Collecting relationships and actual suppression have to be * separate actions. This implementation stores freevars in IdentityHashMap * as keys. For deny, a special DENY object instance is associated. * Otherwise, the structure deps (a linked list) will be the associated * value. * * Each entry is representive of single struct var. * Virtual struct vars will be created, when scope needs to be assigned * to var that already has scope. In this case the link will point * to the old scope and next to the current scope. */ static class StructVar { int deny; // -1 seen, 0 - unseen, 1 - denied StructVar link; // concatenated (old) scope StructVar next; // outer scope StructVar(int deny, StructVar next) { this.deny = deny; this.next = next; } } private static final StructVar DENY = new StructVar(1, null); private static void addFreeVar(Map vars, StructVar deps, YType t, int flags) { if ((flags & RESTRICT_ALL) != 0) { t.flags |= FL_TAINTED_VAR; deps = DENY; } else if ((flags & (RESTRICT_CONTRA | RESTRICT_POLY)) == RESTRICT_CONTRA && (t.flags & FL_TAINTED_VAR) != 0) { deps = DENY; } Object old = vars.put(t, deps); // Non-deny struct flag-var must always get a new instance if (old == null && (flags & STRUCT_VAR) == 0 || deps == DENY) return; if (old == DENY) { deps = DENY; } else { deps = new StructVar(0, deps); deps.link = (StructVar) old; } vars.put(t, deps); } private static void scanFreeVar(Map vars, StructVar deps, YType type, int flags, int depth) { if (type.seen) return; if (type.field >= FIELD_NON_POLYMORPHIC) flags |= (flags & RESTRICT_PROTECT) == 0 ? RESTRICT_ALL : RESTRICT_CONTRA; YType t = type.deref(); int tt = t.type; if (tt == STRUCT || tt == VARIANT) { type.seen = true; scanFreeVar(vars, deps, t.param[0], flags | STRUCT_VAR, depth); deps = (StructVar) vars.get(t.param[0].deref()); for (int i = 1; i < t.param.length; ++i) scanFreeVar(vars, deps, t.param[i], flags, depth); type.seen = false; } else if (tt != VAR) { if (tt == FUN) flags |= RESTRICT_PROTECT; type.seen = true; for (int i = t.param.length; --i >= 0;) { // array/hash value is in mutable store and evil if (i == 0 && tt == FUN) flags |= RESTRICT_CONTRA; else if (i == 1 && tt == MAP && t.param[1].deref() != NO_TYPE) flags |= (flags & RESTRICT_PROTECT) == 0 ? RESTRICT_ALL : RESTRICT_CONTRA; scanFreeVar(vars, deps, t.param[i], flags, depth); } type.seen = false; } else if (t.depth > depth) { addFreeVar(vars, deps, t, flags); } else if ((flags & RESTRICT_ALL) != 0 && t.depth == depth) { t.flags |= FL_TAINTED_VAR; } } private static boolean purgeNonFree(StructVar var) { var.deny = -1; // already seen if (var.next != null && var.next.deny != 0 && (var.next.deny > 0 || purgeNonFree(var.next)) || var.link != null && var.link.deny != 0 && (var.link.deny > 0 || purgeNonFree(var.link))) { var.deny = 1; return true; } return false; } static YType[] getFreeVar(Map vars, YType type, int flags, int depth) { scanFreeVar(vars, null, type, flags, depth); if ((flags & RESTRICT_ALL) != 0) return NO_PARAM; YType[] tv = new YType[vars.size()]; StructVar[] v = new StructVar[tv.length]; int n = 0; for (Iterator i = vars.entrySet().iterator(); i.hasNext(); ++n) { Map.Entry e = (Map.Entry) i.next(); tv[n] = (YType) e.getKey(); StructVar var = v[n] = (StructVar) e.getValue(); if (var != null && var.deny == 0 && purgeNonFree(var)) var.deny = 1; } n = 0; for (int i = 0; i < tv.length; ++i) if (v[i] == null || v[i].deny <= 0) tv[n++] = tv[i]; YType[] result = new YType[n]; System.arraycopy(tv, 0, result, 0, n); return result; } static void getAllTypeVar(List vars, List structs, YType type, boolean freeze) { if (type.seen) return; YType t = type.deref(); if (t.type != VAR) { type.seen = true; int i = -1; if (structs != null && (t.type == STRUCT || t.type == VARIANT)) { getAllTypeVar(structs, null, t.param[i = 0], false); if (freeze) { if (t.allowedMembers == null) t.allowedMembers = t.requiredMembers; else t.requiredMembers = t.allowedMembers; t.flags &= ~FL_FLEX_TYPEDEF; } } while (++i < t.param.length) getAllTypeVar(vars, structs, t.param[i], freeze); type.seen = false; } else if (vars.indexOf(t) < 0) vars.add(t); } static void removeStructs(YType t, Collection vars) { if (!t.seen) { if (t.type != VAR) { int i = 0; if (t.type == STRUCT || t.type == VARIANT) { vars.remove(t.param[0].deref()); i = 1; } else if (t.type == MAP) { // MAP marker type var shouldn't really cause // polymorphism restrictions - no real data associated. vars.remove(t.param[2].deref()); } t.seen = true; while (i < t.param.length) removeStructs(t.param[i++], vars); t.seen = false; } else if (t.ref != null) { removeStructs(t.ref, vars); } } } static void normalizeFlexType(YType t, boolean covariant) { t = t.deref(); if (t.type != VAR && !t.seen) { if ((t.flags & FL_FLEX_TYPEDEF) != 0 && (t.type == STRUCT || t.type == VARIANT)) { Map members = t.requiredMembers; if (t.type == STRUCT ^ members != null ^ covariant && (members == null || t.allowedMembers == null)) { t.requiredMembers = t.allowedMembers; t.allowedMembers = members; } t.flags &= ~FL_FLEX_TYPEDEF; } t.seen = true; for (int i = 0; i < t.param.length; ++i) normalizeFlexType(t.param[i], (i == 0 && t.type == FUN) ^ covariant); t.seen = false; } } // strip == false -> it instead introduces flex types static void stripFlexTypes(YType t, boolean strip) { if (t.type != VAR && !t.seen) { if (strip) t.flags &= ~FL_FLEX_TYPEDEF; else if ((t.type == STRUCT || t.type == VARIANT) && t.requiredMembers == null ^ t.allowedMembers == null) t.flags |= FL_FLEX_TYPEDEF; t.seen = true; for (int i = 0; i < t.param.length; ++i) stripFlexTypes(t.param[i].deref(), strip); t.seen = false; } } static Scope bind(String name, YType valueType, Binder value, int flags, int depth, Scope scope) { scope = new Scope(scope, name, value); scope.free = getFreeVar(new IdentityHashMap(), valueType, flags, depth); // If structure should be polymorphic, // then at least it's flag var will be in free if (scope.free.length == 0) scope.free = null; return scope; } static Scope bindPoly(String name, YType valueType, Binder value, Scope scope) { return bind(name, valueType, value, RESTRICT_POLY, 0, scope); } static YType resolveTypeDef(Scope scope, String name, YType[] param, int depth, TypeNode src, int def) { for (; scope != null; scope = scope.outer) { YType[] typeDef; if (scope.name == name && (typeDef = scope.typedef(true)) != null) { if (typeDef.length - 1 != param.length) throw new CompileException(src, "Type " + name + " expects " + (typeDef.length == 2 ? "1 parameter" : (typeDef.length - 1) + " parameters") + ", not " + param.length); if (scope.free == null) { // shared typedef if (def >= 0 && def != TypeDef.UNSHARE) break; // normal typedef may not use shared ones return typeDef[0]; } if (def == TypeDef.UNSHARE) break; Map vars = createFreeVars(scope.free, depth); for (int i = param.length; --i >= 0;) vars.put(typeDef[i], param[i]); YType res = copyType(typeDef[param.length], vars, new IdentityHashMap()); if (src.exact) stripFlexTypes(res, true); return res; } } throw new CompileException(src, "Unknown type: " + name); } // Used by as cast to mark opaque types as ambigous, allowing them // to later unify with their hidden (wrapped) type. private static void prepareOpaqueCast(YType type, boolean[] known) { if (type.seen) return; YType t = type.deref(); if (t.type != VAR) { type.seen = true; if (t.type >= OPAQUE_TYPES && known[t.type - OPAQUE_TYPES]) t.flags |= FL_AMBIGUOUS_OPAQUE; for (int i = t.param.length; --i >= 0;) prepareOpaqueCast(t.param[i], known); type.seen = false; } } private static Map opaqueMembers(Map src, Map members) { if (src != null) { src = new IdentityHashMap(src); for (Iterator i = src.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next(); Object t = members.get(e.getKey()); if (t != null) e.setValue(t); } } return src; } private static YType deriveOpaque(YType src, YType opaque, Map cache, boolean[] mask) { opaque = opaque.deref(); YType res = (YType) cache.get(opaque); if (res != null) return res; if (opaque.type >= OPAQUE_TYPES && mask[opaque.type - OPAQUE_TYPES]) { cache.put(opaque, opaque); return opaque; } YType s, t; boolean hasOpaque = false; if (opaque.type == STRUCT || opaque.type == VARIANT) { res = new YType(src.type, NO_PARAM); cache.put(opaque, res); Map members = new IdentityHashMap(opaque.allowedMembers != null ? opaque.allowedMembers : opaque.requiredMembers); for (Iterator i = members.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next(); s = (YType) (src.allowedMembers != null ? src.allowedMembers : src.requiredMembers).get(e.getKey()); if (s == null) { i.remove(); continue; } t = deriveOpaque(s.deref(), (YType) e.getValue(), cache, mask); if (t != e.getValue()) { e.setValue(t); hasOpaque = true; } } if (hasOpaque) { res.allowedMembers = opaqueMembers(src.allowedMembers, members); res.requiredMembers = opaqueMembers(src.requiredMembers, members); structParam(res, res.requiredMembers != null ? res.requiredMembers : res.allowedMembers, src.param[0].deref()); if ((res.doc = opaque.doc()) == null) res.doc = src.doc(); return res; } } else if (opaque.type > PRIMITIVE_END && opaque.param.length != 0 && opaque.param.length == src.param.length) { res = new YType(src.type, new YType[src.param.length]); cache.put(opaque, res); for (int i = 0; i < res.param.length; ++i) { s = src.param[i].deref(); t = deriveOpaque(s, opaque.param[i], cache, mask); res.param[i] = t; hasOpaque |= s != t; } if (hasOpaque) { if ((res.doc = opaque.doc()) == null) res.doc = src.doc(); return res; } } else { res = null; } if (res != null) { res.type = VAR; res.ref = src; res.param = null; } cache.put(opaque, src); return src; } static YType opaqueCast(YType from, YType to, Scope scope) throws TypeException { if (from.deref().type == VAR) throw new TypeException("Illegal as cast from 'a to non-Java type"); YType t; boolean[] allow_opaque = new boolean[scope.ctx.opaqueTypes.size() + 1]; for (; scope != null; scope = scope.outer) { YType[] typeDef = scope.typedef(false); if (typeDef != null) { t = typeDef[typeDef.length - 1]; if (t.type >= OPAQUE_TYPES && t.allowedMembers != null) allow_opaque[t.type - OPAQUE_TYPES] = true; } } to = to.deref(); t = copyType(to, null, new IdentityHashMap()); prepareOpaqueCast(t, allow_opaque); unify(from, t); return deriveOpaque(from.deref(), to, new IdentityHashMap(), allow_opaque); } static YType withDoc(YType t, String doc) { if (doc == null) return t; if (t.type > 0 && t.type <= PRIMITIVE_END) { YType tmp = t; t = new YType(0); t.ref = tmp; } t.doc = doc; return t; } }