2
0
Эх сурвалжийг харах

redo type promotion logic, fix bugs

Vladislav Folts 10 жил өмнө
parent
commit
abb6a65a49

BIN
bin/compiled.zip


+ 150 - 170
src/eberon/eberon_context.js

@@ -13,6 +13,7 @@ var eOp = require("js/EberonOperator.js");
 var Symbol = require("js/Symbols.js");
 var Procedure = require("js/Procedure.js");
 var Type = require("js/Types.js");
+var TypePromotion = require("eberon/eberon_type_promotion.js");
 
 /*
 function log(s){
@@ -29,15 +30,6 @@ function superMethodCallGenerator(context, id, type){
     return Procedure.makeProcCallGeneratorWithCustomArgs(context, id, type, args);
 }
 
-function resetPromotedTypes(types){
-    //log("reset promoted: " + types.length);
-    for(var i = 0; i < types.length; ++i){
-        var info = types[i];
-        //log("\t" + info.type.type().name + "->" + info.restore.name);
-        info.type.resetPromotion(info.restore);
-    }
-}
-
 var MethodType = Type.Procedure.extend({
     init: function EberonContext$MethodType(id, type, callGenerator){
         Type.Procedure.prototype.init.call(this);
@@ -130,7 +122,6 @@ var ResultVariable = Type.Variable.extend({
 var TypeNarrowVariable = Type.Variable.extend({
     init: function TypeNarrowVariable(type, isRef, isReadOnly){
         this.__type = type;
-        this.__invertedType = this.__type;
         this.__isRef = isRef;
         this.__isReadOnly = isReadOnly;
     },
@@ -147,17 +138,7 @@ var TypeNarrowVariable = Type.Variable.extend({
         return this.__isReadOnly ? "non-VAR formal parameter"
                                  : Type.Variable.prototype.idType.call(this);
     },
-    promoteType: function(t){
-        var result = this.__type;
-        this.__type = t;
-        return result;
-    },
-    invertType: function(){
-        var type = this.__type;
-        this.__type = this.__invertedType;
-        this.__invertedType = type;
-    },
-    resetPromotion: function(type){
+    setType: function(type){
         this.__type = type;
     }
 });
@@ -220,6 +201,10 @@ var Designator = Context.Designator.extend({
             return this.__beginCall();
         if (msg == Context.endCallMsg)
             return this.__endCall();
+
+        // no type promotion after calling functions
+        if (breakTypePromotion(msg))
+            return;
         
         return Context.Designator.prototype.handleMessage.call(this, msg);
     },
@@ -316,7 +301,6 @@ var InPlaceVariableInitFor = InPlaceVariableInit.extend({
 var ExpressionProcedureCall = Context.Chained.extend({
     init: function EberonContext$init(context){
         Context.Chained.prototype.init.call(this, context);
-        this.__typePromotion = new TypePromotionHandler();
     },
     setDesignator: function(d){
         var info = d.info();
@@ -325,15 +309,6 @@ var ExpressionProcedureCall = Context.Chained.extend({
             parent.handleExpression(info.expression());
         else
             parent.setDesignator(d);
-    },
-    handleMessage: function(msg){
-        if (this.__typePromotion.handleMessage(msg))
-            return;
-
-        return Context.Chained.prototype.handleMessage.call(this, msg);
-    },
-    endParse: function(){
-        this.__typePromotion.reset();
     }
 });
 
@@ -607,11 +582,23 @@ var RecordDecl = Context.RecordDecl.extend({
     }
 });
 
-function resetTypePromotionMadeInSeparateStatement(msg){
-    if (!(msg instanceof TransferPromotedTypesMsg))
-        return false;
-    resetPromotedTypes(msg.types);
-    return true;
+function breakTypePromotion(msg){
+    if (msg instanceof TransferPromotedTypesMsg){
+        msg.promotion.clear();
+        return true;
+    }
+    if (msg instanceof PromoteTypeMsg)
+        return true;
+}
+
+function handleTypePromotionMadeInSeparateStatement(msg){
+    if (breakTypePromotion(msg))
+        return true;
+    if (msg instanceof BeginTypePromotionOrMsg){
+        msg.result = new TypePromotion.OrPromotions();
+        return true;
+    }
+    return false;
 }
 
 var ProcOrMethodDecl = Context.ProcDecl.extend({
@@ -653,7 +640,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
             return undefined;
         }
 
-        if (resetTypePromotionMadeInSeparateStatement(msg))
+        if (handleTypePromotionMadeInSeparateStatement(msg))
             return;
 
         return Context.ProcDecl.prototype.handleMessage.call(this, msg);
@@ -719,28 +706,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
     }
 });
 
-var Factor = Context.Factor.extend({
-    init: function EberonContext$Factor(context){
-        Context.Factor.prototype.init.call(this, context);
-        this.__typePromotion = new TypePromotionHandler();
-        this.__invert = false;
-    },
-    handleMessage: function(msg){
-        if (this.__typePromotion.handleMessage(msg))
-            return;
-        return Context.Factor.prototype.handleMessage.call(this, msg);
-    },
-    handleLiteral: function(s){
-        if (s == "~")
-            this.__invert = !this.__invert;
-        Context.Factor.prototype.handleLiteral.call(this, s);
-    },
-    endParse: function(){
-        if (this.__invert)
-            this.__typePromotion.invert();
-        this.__typePromotion.transferTo(this.parent());
-    }
-});
+var Factor = Context.Factor;
 
 var AddOperator = Context.AddOperator.extend({
     init: function EberonContext$AddOperator(context){
@@ -771,13 +737,8 @@ function PromoteTypeMsg(info, type){
     this.type = type;
 }
 
-function resetTypePromotionMsg(){}
-function resetInvertedPromotedTypesMsg(){}
-function invertTypePromotionMsg(){}
-
-function TransferPromotedTypesMsg(types, invertTypes){
-    this.types = types;
-    this.invertTypes = invertTypes;
+function TransferPromotedTypesMsg(promotion){
+    this.promotion = promotion;
 }
 
 var RelationOps = Context.RelationOps.extend({
@@ -834,47 +795,103 @@ var RelationOps = Context.RelationOps.extend({
     }
 });
 
+function BeginTypePromotionAndMsg(){
+    this.result = undefined;
+}
+
+function BeginTypePromotionOrMsg(){
+    this.result = undefined;
+}
+
 var Term = Context.Term.extend({
     init: function EberonContext$Term(context){
         Context.Term.prototype.init.call(this, context);
-        this.__invertedTypePromotionReset = false;
+        this.__typePromotion = undefined;
+        this.__currentPromotion = undefined;
+        this.__andHandled = false;
+    },
+    handleMessage: function(msg){
+        if (msg instanceof PromoteTypeMsg) {
+            var promoted = msg.info;
+            var p = this.__getCurrentPromotion();
+            if (p)
+                p.promote(promoted, msg.type);
+            return;
+        }
+        if (msg instanceof BeginTypePromotionOrMsg){
+            var cp = this.__getCurrentPromotion();
+            if (cp)
+                msg.result = cp.makeOr();
+            return;
+        }
+        return Context.Term.prototype.handleMessage.call(this, msg);
     },
     handleLogicalAnd: function(){
-        if (!this.__invertedTypePromotionReset){
-            this.handleMessage(resetInvertedPromotedTypesMsg);
-            this.__invertedTypePromotionReset = true;
+        if (this.__typePromotion)
+            this.__currentPromotion = this.__typePromotion.next();
+        else
+            this.__andHandled = true;
+    },
+    handleLogicalNot: function(){
+        Context.Term.prototype.handleLogicalNot.call(this);
+        var p = this.__getCurrentPromotion();
+        if (p)
+            p.invert();
+    },
+    __getCurrentPromotion: function(){
+        if (!this.__currentPromotion){
+            var msg = new BeginTypePromotionAndMsg();
+            this.parent().handleMessage(msg);
+            this.__typePromotion = msg.result;
+            if (this.__typePromotion){
+                if (this.__andHandled)
+                    this.__typePromotion.next();
+                this.__currentPromotion = this.__typePromotion.next();
+            }
         }
+        return this.__currentPromotion;
     }
 });
 
 var SimpleExpression = Context.SimpleExpression.extend({
     init: function EberonContext$SimpleExpression(context){
         Context.SimpleExpression.prototype.init.call(this, context);
-        this.__typePromotion = new TypePromotionHandler();
-        this.__orTypePromotion = this.__typePromotion;
+        this.__typePromotion = undefined;
+        this.__currentTypePromotion = undefined;
+        this.__orHandled = false;
     },
     handleLogicalOr: function(){
-        this.__orTypePromotion.reset();
-        this.__orTypePromotion.invert();
-        if (this.__orTypePromotion != this.__typePromotion)
-            this.__typePromotion.transferFrom(this.__orTypePromotion);
-        this.__orTypePromotion = new TypePromotionHandler();
+        if (this.__typePromotion)
+            this.__currentPromotion = this.__typePromotion.next();
+        else
+            this.__orHandled = true;
     },
     handleMessage: function(msg){
-        if (this.__orTypePromotion.handleMessage(msg))
+        if (msg instanceof BeginTypePromotionAndMsg){
+            var p = this.__getCurrentPromotion();
+            if (p)
+                msg.result = p.makeAnd();
             return;
+        }
         return Context.SimpleExpression.prototype.handleMessage.call(this, msg);
     },
     endParse: function(){
-        if (this.__orTypePromotion != this.__typePromotion){
-
-            //this.__orTypePromotion.reset();
-            this.__orTypePromotion.invert();
-            this.__typePromotion.transferFrom(this.__orTypePromotion);
-            this.__typePromotion.invert();
-        }
-        this.__typePromotion.transferTo(this.parent());
+        if (this.__typePromotion)
+            this.parent().handleTypePromotion(this.__typePromotion);
         Context.SimpleExpression.prototype.endParse.call(this);
+    },
+    __getCurrentPromotion: function(){
+        if (!this.__currentPromotion){
+            var msg = new BeginTypePromotionOrMsg();
+            this.parent().handleMessage(msg);
+            this.__typePromotion = msg.result;
+            if (this.__typePromotion){
+                if (this.__orHandled)
+                    this.__typePromotion.next();
+                this.__currentPromotion = this.__typePromotion.next();
+            }
+        }
+        return this.__currentPromotion;
     }
 });
 
@@ -883,103 +900,62 @@ var relationOps = new RelationOps();
 var Expression = Context.Expression.extend({
     init: function EberonContext$Expression(context){
         Context.Expression.prototype.init.call(this, context, relationOps);
-        this.__typePromotion = new TypePromotionHandler();
+        this.__typePromotion = undefined;
+        this.__currentTypePromotion = undefined;
     },
     handleMessage: function(msg){
-        if (this.__typePromotion.handleMessage(msg))
+        if (msg instanceof TransferPromotedTypesMsg)
             return;
-
         return Context.Expression.prototype.handleMessage.call(this, msg);
     },
+    handleTypePromotion: function(t){
+        this.__currentTypePromotion = t;
+    },
     handleLiteral: function(s){
-        this.__typePromotion.reset();
+        if (this.__currentTypePromotion){
+            this.__currentTypePromotion.clear();
+        }
         Context.Expression.prototype.handleLiteral.call(this, s);
     },
     endParse: function(){
-        this.__typePromotion.transferTo(this.parent());
+        if (this.__currentTypePromotion)
+            this.parent().handleMessage(new TransferPromotedTypesMsg(this.__currentTypePromotion));
         return Context.Expression.prototype.endParse.call(this);
     }
 });
 
-//var id = 0;
+var OperatorScopes = Class.extend({
+    init: function EberonContext$OperatorScopes(context){
+        this.__context = context;
+        this.__scope = undefined;
 
-var TypePromotionHandler = Class.extend({
-    init: function EberonContext$TypePromotionHandler(){
-        this.__promotedTypes = [];
-        this.__invertPromotedTypes = [];
-        //this.__id = id++;
+        this.__typePromotion = undefined;
+        this.__typePromotions = [];
+        this.__ignorePromotions = false;
+        this.alternate();
     },
     handleMessage: function(msg){
+        if (this.__ignorePromotions)
+            return false;
+        if (msg instanceof TransferPromotedTypesMsg)
+            return true;
         if (msg instanceof PromoteTypeMsg){
-            var promoted = msg.info;
-            var restore = promoted.promoteType(msg.type);
-            //log("promote: " + restore.name + "->" + msg.type.name + ", " + this.__id);
-            this.__promotedTypes.push({type: promoted, restore: restore});
+            this.__typePromotion = new TypePromotion.Promotion(msg.info, msg.type);
+            this.__typePromotions.push(this.__typePromotion);
             return true;
         }
-        if (msg instanceof TransferPromotedTypesMsg){
-            this.__transferFrom(msg.types, msg.invertTypes);
+        if (msg instanceof BeginTypePromotionOrMsg){
+            this.__typePromotion = new TypePromotion.OrPromotions();
+            this.__typePromotions.push(this.__typePromotion);
+            msg.result = this.__typePromotion;
             return true;
         }
-        switch (msg){
-            case resetTypePromotionMsg:
-                this.reset();
-                return true;
-            case invertTypePromotionMsg:
-                this.invert();
-                return true;
-            case resetInvertedPromotedTypesMsg:
-                this.resetInverted();
-                return true;
-        }
-    return false;
-    },
-    invert: function(){
-        var all = this.__promotedTypes.concat(this.__invertPromotedTypes);
-        //log("invert: " + all.length + ", " + this.__id);
-        for(var i = 0; i < all.length; ++i){
-            //log("\t" + all[i].type.type().name + ", " + this.__id);
-            all[i].type.invertType();
-        }
-
-        var swap = this.__promotedTypes;
-        this.__promotedTypes = this.__invertPromotedTypes;
-        this.__invertPromotedTypes = swap;
-    },
-    reset: function(){
-        resetPromotedTypes(this.__promotedTypes);
-        this.__promotedTypes = [];
-    },
-    resetInverted: function(inverted){
-        resetPromotedTypes(this.__invertPromotedTypes);
-        this.__invertPromotedTypes = [];
-    },
-    transferTo: function(context){
-        if (this.__promotedTypes.length || this.__invertPromotedTypes.length)
-            context.handleMessage(new TransferPromotedTypesMsg(this.__promotedTypes, this.__invertPromotedTypes));
-    },
-    transferFrom: function(other){
-        this.__transferFrom(other.__promotedTypes, this.__invertPromotedTypes);
-    },
-    __transferFrom: function(promotedTypes, invertPromotedTypes){
-        //log("transfer, " + this.__id);
-        Array.prototype.push.apply(this.__promotedTypes, promotedTypes);
-        Array.prototype.push.apply(this.__invertPromotedTypes, invertPromotedTypes);
-    }
-});
-
-var OperatorScopes = Class.extend({
-    init: function EberonContext$OperatorScopes(context){
-        this.__context = context;
-        this.__scope = undefined;
-
-        this.__typePromotion = new TypePromotionHandler();
-        this.__typePromotions = [this.__typePromotion];
-
-        this.alternate();
+        return false;
     },
-    handleMessage: function(msg){
-        return this.__typePromotion.handleMessage(msg);
+    doThen: function(){
+        if (this.__typePromotion)
+            this.__typePromotion.and();
+        this.__ignorePromotions = true;
     },
     alternate: function(){
         if (this.__scope)
@@ -989,14 +965,15 @@ var OperatorScopes = Class.extend({
             this.__context.language().stdSymbols);
         this.__context.pushScope(this.__scope);
 
-        this.__typePromotion.reset();
-        this.__typePromotion.invert();
-        this.__typePromotion = new TypePromotionHandler();
-        this.__typePromotions.push(this.__typePromotion);
+        if (this.__typePromotion){
+            this.__typePromotion.reset();
+            this.__typePromotion.or();
+            this.__typePromotion = undefined;
+        }
+        this.__ignorePromotions = false;
     },
     reset: function(){
         this.__context.popScope();
-
         for(var i = 0; i < this.__typePromotions.length; ++i){
             this.__typePromotions[i].reset();
         }
@@ -1010,7 +987,9 @@ var While = Context.While.extend({
     },
     handleLiteral: function(s){
         Context.While.prototype.handleLiteral.call(this, s);
-        if (s == "ELSIF")
+        if (s == "DO")
+            this.__scopes.doThen();
+        else if (s == "ELSIF")
             this.__scopes.alternate();
     },
     handleMessage: function(msg){
@@ -1038,7 +1017,9 @@ var If = Context.If.extend({
     },
     handleLiteral: function(s){
         Context.If.prototype.handleLiteral.call(this, s);
-        if (s == "ELSIF" || s == "ELSE")
+        if (s == "THEN")
+            this.__scopes.doThen();
+        else if (s == "ELSIF" || s == "ELSE")
             this.__scopes.alternate();
     },
     endParse: function(){
@@ -1200,9 +1181,8 @@ var ModuleDeclaration = Context.ModuleDeclaration.extend({
         Context.ModuleDeclaration.prototype.init.call(this, context);
     },
     handleMessage: function(msg){
-        if (resetTypePromotionMadeInSeparateStatement(msg))
+        if (handleTypePromotionMadeInSeparateStatement(msg))
             return;
-
         return Context.ModuleDeclaration.prototype.handleMessage.call(this, msg);
     }
 });

+ 140 - 0
src/eberon/eberon_type_promotion.js

@@ -0,0 +1,140 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+
+function log(s){
+    //console.info(s);
+}
+
+function assert(condition){
+    if (!condition)
+        throw new Error("assertion failed");
+}
+
+var Promotion = Class.extend({
+    init: function EberonTypePromotion$Promotion(v, type, inverted){
+        this.__v = v;
+        this.__type = type;
+        this.__originalType = v.type();
+        this.__inverted = inverted;
+    },
+    and: function(){
+        if (!this.__inverted)
+            this.__v.setType(this.__type);
+    },
+    or: function(){
+        if (this.__inverted)
+            this.__v.setType(this.__type);
+    },
+    reset: function(){
+        this.__v.setType(this.__originalType);
+    },
+    invert: function(){
+        this.__inverted = !this.__inverted;
+    }
+});
+
+var MaybePromotion = Class.extend({
+    init: function EberonTypePromotion$Promotion(handle){
+        this.__inverted = false;
+        this.__handle = handle;
+    },
+    promote: function(v, type){
+        log("promote: " + type.name);
+        this.__handle(new Promotion(v, type, this.__inverted));
+    },
+    invert: function(){
+        this.__inverted = !this.__inverted;
+    },
+    makeOr: function(){
+        var result = new OrPromotions(this.__inverted);
+        this.__handle(result);
+        return result;
+    },
+    makeAnd: function(){
+        var result = new AndPromotions(this.__inverted);
+        this.__handle(result);
+        return result;
+    }
+});
+
+var CombinedPromotion = Class.extend({
+    init: function EberonTypePromotion$CombinedPromotion(op, invertedOp, inverted){
+        this.__op = op;
+        this.__invertedOp = invertedOp;
+        this.__inverted = inverted;
+        this.__promotions = [];
+        this.__current = undefined;
+        this.__count = 0;
+    },
+    and: function(){
+        log("combined and(" + this.__op + "), inverted: " + this.__inverted);
+        if (this.__inverted)
+            this.__applyForAll();
+        else
+            this.__applyIfSingle();
+    },
+    or: function(){
+        log("combined or(" + this.__op + "), inverted: " + this.__inverted);
+        if (this.__inverted)
+            this.__applyIfSingle();
+        else
+            this.__applyForAll();
+    },
+    reset: function(){
+        for(var i = this.__promotions.length; i--;){
+            var p = this.__promotions[i];
+            p.reset();
+        }
+    },
+    clear: function(){
+        this.reset();
+        this.__promotions = [];
+        this.__current = undefined;
+        this.__count = 0;
+    },
+    next: function(p){
+        log("next " + this.__op + ": " + this.__current);
+        if (this.__current)
+            this.__current[this.__op]();
+
+        this.__current = undefined;
+        ++this.__count;
+
+        return new MaybePromotion(this.__handlePromotion.bind(this));
+    },
+    __applyForAll: function(){
+        for(var i = 0; i < this.__promotions.length; ++i)
+            this.__promotions[i][this.__op]();
+    },
+    __applyIfSingle: function(op){
+        log("applyIfSingle: " + this.__count);
+        if (this.__count > 1)
+            this.reset();
+        else if (this.__current)
+            this.__current[this.__invertedOp]();
+    },
+    __handlePromotion: function(p){
+        assert(!this.__current);
+        this.__promotions.push(p);
+        this.__current = p;
+    }
+});
+
+var AndPromotions = CombinedPromotion.extend({
+    init: function EberonTypePromotion$AndPromotions(inverted){
+        log("AndPromotions");
+        CombinedPromotion.prototype.init.call(this, "and", "or", !inverted);
+    }
+});
+
+var OrPromotions = CombinedPromotion.extend({
+    init: function EberonTypePromotion$OrPromotions(inverted){
+        log("OrPromotions");
+        CombinedPromotion.prototype.init.call(this, "or", "and", inverted);
+    }
+});
+
+exports.Promotion = Promotion;
+exports.AndPromotions = AndPromotions;
+exports.OrPromotions = OrPromotions;

+ 19 - 1
test/test_unit_common.js

@@ -38,7 +38,8 @@ var TestContext = Context.Context.extend({
                 });
         this.pushScope(Scope.makeModule("test", language.stdSymbols));
     },
-    qualifyScope: function(){return "";}
+    qualifyScope: function(){return "";},
+    handleMessage: function(){}
 });
 
 function makeContext(language){return new TestContext(language);}
@@ -172,6 +173,23 @@ function testWithModule(src, language, pass, fail){
         fail);
 }
 
+function nthLine(s, n){
+    var result = 0;
+    while (n--)
+        result = s.indexOf('\n', result) + 1;
+    return result;
+}
+
+function assert(cond){
+    if (!cond){
+        var stack = new Error().stack;
+        var from = nthLine(stack, 2);
+        stack = stack.substring(from, stack.indexOf('\n', from));
+        throw new TestError("assertion failed: " + stack);
+    }
+}
+
+exports.assert = assert;
 exports.context = context;
 exports.pass = pass;
 exports.fail = fail;

+ 154 - 16
test/test_unit_eberon.js

@@ -1,8 +1,11 @@
 "use strict";
 
+var Class = require("rtl.js").Class;
 var language = require("eberon/eberon_grammar.js").language;
 var TestUnitCommon = require("test_unit_common.js");
+var TypePromotion = require("eberon/eberon_type_promotion.js");
 
+var assert = TestUnitCommon.assert;
 var pass = TestUnitCommon.pass;
 var fail = TestUnitCommon.fail;
 var context = TestUnitCommon.context;
@@ -64,6 +67,14 @@ var temporaryValues = {
     }
 };
 
+var TestVar = Class.extend({
+    init: function(){
+        this.__type = "type";
+    },
+    type: function(){return this.__type;},
+    setType: function(type){this.__type = type;}
+});
+
 exports.suite = {
 "arithmetic operators": testWithContext(
     context(grammar.statement, "VAR b1: BOOLEAN;"),
@@ -434,6 +445,124 @@ exports.suite = {
          ["returnProc()", "procedure returning a result cannot be used as a statement"] // call is not applied implicitly to result
         )
     ),
+"type promotion": {
+    "or" : pass(
+        function(){
+            var or = new TypePromotion.OrPromotions();
+            var a = new TestVar();
+            var p = or.next();
+            assert(a.type() == "type");
+            p.invert();
+            p.promote(a, "type1");
+            assert(a.type() == "type");
+            or.next();
+            assert(a.type() == "type1");
+            or.reset();
+            assert(a.type() == "type");
+        },
+        function(){
+            var or = new TypePromotion.OrPromotions();
+            var a = new TestVar();
+            var p = or.next(p);
+            p.promote(a, "type1");
+            or.next();
+            assert(a.type() == "type");
+        },
+        function(){
+            var or = new TypePromotion.OrPromotions();
+            var a = new TestVar();
+            var p1 = or.next();
+            p1.promote(a, "type1");
+            var p2 = or.next();
+            p2.invert();
+            p2.promote(a, "type2");
+            assert(a.type() == "type");
+            assert(a.type() == "type");
+            or.next();
+            assert(a.type() == "type2");
+            or.reset();
+            assert(a.type() == "type");
+        }
+    ),
+    "and": pass(
+        function(){
+            var and = new TypePromotion.AndPromotions();
+            var a = new TestVar();
+            var p = and.next();
+            p.promote(a, "type1");
+            and.next();
+            assert(a.type() == "type1");
+            and.reset();
+            assert(a.type() == "type");
+        },
+        function(){ // (a IS type1) & (v OR (a IS type2)) & v
+            var and = new TypePromotion.AndPromotions();
+            var a = new TestVar();
+            var p = and.next();
+            p.promote(a, "type1");
+            var subOr = and.next().makeOr();
+            subOr.next();
+            subOr.next().promote(a, "type2");
+            and.next();
+            assert(a.type() == "type1");
+            and.reset();
+            assert(a.type() == "type");
+        },
+        function(){ // (a IS type1) & ~(v OR ~(a IS type2)) & v
+            var and = new TypePromotion.AndPromotions();
+            var a = new TestVar();
+            and.next().promote(a, "type1");
+            var subOr = and.next();
+            subOr.invert();
+            subOr = subOr.makeOr();
+            subOr.next();
+            var p = subOr.next();
+            p.invert();
+            p.promote(a, "type2");
+            and.next();
+            assert(a.type() == "type2");
+            and.reset();
+            assert(a.type() == "type");
+        },
+        function(){ // (a IS type1) & (v & (a IS type2))
+            var and = new TypePromotion.AndPromotions();
+            var a = new TestVar();
+            and.next().promote(a, "type1");
+            var sub = and.next().makeAnd();
+            sub.next();
+            assert(a.type() == "type1");
+            sub.next().promote(a, "type2");
+            assert(a.type() == "type1");
+            and.and();
+            assert(a.type() == "type2");
+            and.or();
+            assert(a.type() == "type");
+        },
+        function(){ // (~(~(a IS type1)) & v) OR v
+            var a = new TestVar();
+            var or = new TypePromotion.OrPromotions();
+            var and = or.next().makeAnd();
+            var p1 = and.next();
+            p1.invert();
+            var p2 = p1.makeOr().next().makeAnd().next();
+            p2.invert();
+            p2.promote(a, "type1");
+            and.next();
+            assert(a.type() == "type1");
+            or.next();
+            assert(a.type() == "type");
+        },
+        function(){ // (v OR (a IS type1)) & v)
+            var a = new TestVar();
+            var and = new TypePromotion.AndPromotions();
+            var or = and.next().makeOr();
+            or.next();
+            or.next().makeAnd().next().promote(a, "type1");
+            and.next();
+            assert(a.type() == "type");
+        }
+    )
+},
 "in place variables": {
     "initialization": testWithContext(
         context(grammar.statement,
@@ -491,18 +620,20 @@ exports.suite = {
     "type promotion in expression": testWithContext(
         temporaryValues.context,
         temporaryValues.passExpressions(
-             "b IS PDerived",
-             "(b IS PDerived) & b.flag",
-             "(b IS PDerived) & bVar & b.flag",
-             "(b IS PDerived) & (bVar OR b.flag)",
-             "(b IS PDerived) & (b2 IS PDerived) & b.flag & b2.flag",
-             "(b IS PDerived) & proc(TRUE) & b.flag",
-             "(b IS PDerived) & ~proc(TRUE) & b.flag",
-             "~(~(b IS PDerived)) & b.flag",
-             "~~(b IS PDerived) & b.flag",
-             "(b IS PDerived) & ((b IS PDerived2) OR bVar) & b.flag"
-             //TODO: "((b IS PDerived) = TRUE) & b.flag); END p;",
-             ),
+            "(b IS PDerived) & b.flag",
+            "(b IS PDerived) & bVar & b.flag",
+            "(b IS PDerived) & (bVar OR b.flag)",
+            "(b IS PDerived) & (b2 IS PDerived) & b.flag & b2.flag",
+            "(b IS PDerived) & proc(TRUE) & b.flag",
+            "(b IS PDerived) & ~proc(TRUE) & b.flag",
+            "~(~(b IS PDerived)) & b.flag",
+            "~~(b IS PDerived) & b.flag",
+            "(b IS PDerived) & ((b IS PDerived2) OR bVar) & b.flag",
+            "(b IS PDerived) & (bVar OR (b IS PDerived2)) & b.flag",
+            "(b IS PDerived) & ~(bVar OR ~(b IS PDerived2)) & b.flag2",
+            "~(bVar & (b IS PDerived)) OR b.flag"
+            //TODO: "((b IS PDerived) = TRUE) & b.flag); END p;",
+            ),
         temporaryValues.failExpressions(
             "(b IS PDerived) OR b.flag",
             "(bVar OR (b IS PDerived)) & b.flag",
@@ -512,10 +643,12 @@ exports.suite = {
              "proc(b IS PDerived) & proc(b.flag)",
              "ORD(b IS PDerived) * ORD(b.flag) = 0",
              "((b IS PDerived) = FALSE) & b.flag",
+             "((b IS PDerived) & bVar) = b.flag",
              "b IS PDerived); ASSERT(b.flag",
              "((b IS PDerived) OR (b IS PDerived)) & b.flag",
              "(b IS PDerived) OR (b IS PDerived) OR b.flag",
-             "(bVar OR (b IS PDerived)) & b.flag"
+             "(bVar OR (b IS PDerived)) & b.flag",
+             "~(bVar & ~(b IS PDerived)) & b.flag"
              )
         ),
     "invert type promotion in expression": testWithContext(
@@ -546,7 +679,10 @@ exports.suite = {
     "type promotion in separate statements": testWithContext(
         temporaryValues.context,
         pass(),
-        temporaryValues.failStatements("bVar := b IS PDerived; ASSERT(b.flag)")
+        temporaryValues.failStatements(
+            "bVar := b IS PDerived; ASSERT(b.flag)",
+            "bVar := (b IS PDerived) & bVar; ASSERT(b.flag)"
+            )
         ),
     "type promotion in IF": testWithContext(
         temporaryValues.context,
@@ -557,7 +693,8 @@ exports.suite = {
             "IF FALSE THEN ELSIF b IS PDerived THEN b.flag := FALSE; END;",
             "IF b IS PDerived THEN bVar := (b IS PDerived2) & b.flag2; b.flag := FALSE; END;",
             "IF bVar THEN ELSIF b IS PDerived2 THEN ELSIF b IS PDerived THEN END;",
-            "IF bVar THEN ELSIF b IS PDerived THEN ELSIF b IS PDerived THEN ELSIF b IS PDerived THEN END;"
+            "IF bVar THEN ELSIF b IS PDerived THEN ELSIF b IS PDerived THEN ELSIF b IS PDerived THEN END;",
+            "IF b IS PDerived THEN IF bVar OR (b IS PDerived2) THEN b.flag := FALSE; END; END"
             ),
         temporaryValues.failStatements(
             "IF (b IS PDerived) OR bVar THEN b.flag := FALSE; END",
@@ -578,7 +715,8 @@ exports.suite = {
             "IF bVar OR (b IS PDerived) THEN b.flag := FALSE; END;",
             "IF bVar OR (b IS PDerived) THEN ELSE b.flag := FALSE; END;",
             "IF bVar OR ~(b IS PDerived) THEN b.flag := FALSE; END;",
-            "IF b IS PDerived THEN ELSIF TRUE THEN b.flag := FALSE; END"
+            "IF b IS PDerived THEN ELSIF TRUE THEN b.flag := FALSE; END",
+            "IF bVar THEN bVar := (b IS PDerived) & bVar; ASSERT(b.flag); END"
              )
         ),
     "invert type promotion in IF": testWithContext(