ソースを参照

Fix typeguard for value variables (Issue #16).

Vladislav Folts 12 年 前
コミット
b486363839
7 ファイル変更63 行追加26 行削除
  1. 2 2
      src/code.js
  2. 20 8
      src/context.js
  3. 8 6
      src/operator.js
  4. 9 4
      src/type.js
  5. 1 1
      test/expected/cast.js
  6. 1 1
      test/input/cast.ob
  7. 22 4
      test/test_unit.js

+ 2 - 2
src/code.js

@@ -59,7 +59,7 @@ var Expression = Class.extend({
         if (this.__type instanceof Type.Array || this.__type instanceof Type.Record)
             return this;
         var info = this.__designator.info();
-        if (!(info instanceof Type.Variable && info.isVar()))
+        if (!(info instanceof Type.VariableRef))
             return this;
         return new Expression(this.__code + ".get()", this.__type);
     },
@@ -68,7 +68,7 @@ var Expression = Class.extend({
             return this;
         
         var info = this.__designator.info();
-        if (info instanceof Type.Variable && info.isVar())
+        if (info instanceof Type.VariableRef)
             return this;
 
         return new Expression(this.__designator.refCode(), this.__type);

+ 20 - 8
src/context.js

@@ -261,7 +261,7 @@ exports.Identdef = ChainedContext.extend({
 });
 
 exports.Designator = ChainedContext.extend({
-    init: function DesignatorContext(context){
+    init: function Context$Designator(context){
         ChainedContext.prototype.init.call(this, context);
         this.__currentType = undefined;
         this.__info = undefined;
@@ -284,14 +284,19 @@ exports.Designator = ChainedContext.extend({
     },
     setIdent: function(id){
         var t = this.__currentType;
-        if (t instanceof Type.Pointer)
+        var isReadOnly = this.__info instanceof Type.Variable 
+                      && this.__info.isReadOnly();
+        if (t instanceof Type.Pointer){
             this.__handleDeref();
+            isReadOnly = false;
+        }
         else if (!(t instanceof Type.Record
                 || t instanceof Type.Module
                 || t instanceof Module.AnyType))
             throw new Errors.Error("cannot designate '" + t.description() + "'");
 
         this.__denote(id);
+        this.__info = new Type.Variable(this.__currentType, isReadOnly);
         this.__scope = undefined;
     },
     codeGenerator: function(){return this.__code;},
@@ -310,8 +315,9 @@ exports.Designator = ChainedContext.extend({
             throw new Errors.Error("index out of bounds: maximum possible index is "
                                  + (type.length() - 1)
                                  + ", got " + value );
+
         this.__currentType = type.elementsType();
-        this.__info = new Type.Variable(this.__currentType, false, this.__info.isReadOnly());
+        this.__info = new Type.Variable(this.__currentType, this.__info.isReadOnly());
     },
     handleLiteral: function(s){
         if (s == "]" || s == ","){
@@ -324,18 +330,22 @@ exports.Designator = ChainedContext.extend({
             this.__derefCode = this.__code.result();
             this.__code = new Code.SimpleGenerator();
         }
-        else if (s == "^")
+        else if (s == "^"){
             this.__handleDeref();
+            this.__info = new Type.VariableRef(this.__currentType);
+        }
     },
     __handleDeref: function(){
         if (!(this.__currentType instanceof Type.Pointer))
             throw new Errors.Error("POINTER TO type expected, got '"
                                  + this.__currentType.description() + "'");
         this.__currentType = this.__currentType.baseType();
-        this.__info = new Type.Variable(this.__currentType, false, false);
     },
     handleTypeCast: function(type){
         if (this.__currentType instanceof Type.Record){
+            if (!(this.__info instanceof Type.VariableRef))
+                throw new Errors.Error(
+                    "invalid type cast: a value variable and cannot be used in typeguard");
             if (!(type instanceof Type.Record))
                 throw new Errors.Error(
                     "invalid type cast: RECORD type expected as an argument of RECORD type guard, got '"
@@ -378,7 +388,7 @@ exports.Designator = ChainedContext.extend({
             return this.rtl().makeRef(this.__derefCode, this.__propCode);
         if (!(this.__currentType instanceof Type.Array)
             && !(this.__currentType instanceof Type.Record)
-            && this.__info instanceof Type.Variable && !this.__info.isVar())
+            && !(this.__info instanceof Type.VariableRef))
             return "{set: function($v){" + code + " = $v;}, get: function(){return " + code + ";}}";
         return code;
     }
@@ -486,7 +496,9 @@ exports.ProcDecl = ChainedContext.extend({
         if (name == this.__id.id())
             throw new Errors.Error("argument '" + name + "' has the same name as procedure");
         var readOnly = !arg.isVar && (arg.type instanceof Type.Array);
-        var s = new Symbol.Symbol(name, new Type.Variable(arg.type, arg.isVar, readOnly));
+        var v = arg.isVar ? new Type.VariableRef(arg.type)
+                          : new Type.Variable(arg.type, readOnly);
+        var s = new Symbol.Symbol(name, v);
         this.currentScope().addSymbol(s);
 
         var code = this.codeGenerator();
@@ -812,7 +824,7 @@ exports.MulOperator = ChainedContext.extend({
 
 function writeDerefDesignatorCode(designator, code){
     var info = designator.info();
-    if (info instanceof Type.Variable && info.isVar())
+    if (info instanceof Type.VariableRef)
         code.write(".get()");
 }
 

+ 8 - 6
src/operator.js

@@ -92,34 +92,36 @@ function assign(left, right, context){
                              + "' is '" + leftType.description()
                              + "' and cannot be assigned to '" + rightType.description() + "' expression");
 
-    if (isArray && rightType instanceof Type.Array)
+    if (isArray && rightType instanceof Type.Array){
         if (leftType.length() === undefined)
             throw new Errors.Error("'" + leftCode
                                  + "' is open '" + leftType.description()
                                  + "' and cannot be assigned");
-        else if (rightType.length() === undefined)
+        if (rightType.length() === undefined)
             throw new Errors.Error("'" + leftCode
                                  + "' cannot be assigned to open '"
                                  + rightType.description() + "'");
-        else if (leftType.length() != rightType.length())
+        if (leftType.length() != rightType.length())
             throw new Errors.Error("array size mismatch: '" + leftCode
                                  + "' has size " + leftType.length()
                                  + " and cannot be copied to the array with size "
                                  + rightType.length());
+    }
     
     if (isArray || rightType instanceof Type.Record)
         return context.rtl().copy(rightCode, leftCode);
 
     var castCode = castOperation(context, right.deref()).code();
     rightCode = castCode ? castCode : rightCode;
-    return leftCode + (info.isVar() ? ".set(" + rightCode + ")"
-                                    : " = " + rightCode);
+    return leftCode + (info instanceof Type.VariableRef 
+                      ? ".set(" + rightCode + ")"
+                      : " = " + rightCode);
 }
 
 function makeInplace(code, altOp){
     return function(left, right){
         var info = left.designator().info();
-        if (info.isVar())
+        if (info instanceof Type.VariableRef)
             return assign(left, altOp(left, right));
         return left.code() + code + right.deref().code();
     };

+ 9 - 4
src/type.js

@@ -153,21 +153,25 @@ exports.Const = Id.extend({
 });
 
 var Variable = Id.extend({
-    init: function Variable(type, isVar, isReadOnly){
+    init: function Variable(type, isReadOnly){
         Id.prototype.init.call(this);
         this.__type = type;
-        this.__isVar = isVar;
         this.__isReadOnly = isReadOnly;
     },
     idType: function(){return this.__isReadOnly ? "read-only variable" : "variable";},
     type: function(){return this.__type;},
-    isVar: function(){return this.__isVar;},
     isReadOnly: function(){return this.__isReadOnly;}
 });
 
+var VariableRef = Variable.extend({
+    init: function Type$VariableRef(type){
+        Variable.prototype.init.call(this, type, false);
+    }
+});
+
 var ExportedVariable = Variable.extend({
     init: function ExportedVariable(variable){
-        Variable.prototype.init.call(this, variable.type(), variable.isVar(), true);
+        Variable.prototype.init.call(this, variable.type(), true);
     },
     idType: function(){return "imported variable";}
 });
@@ -188,6 +192,7 @@ var Module = Id.extend({
 });
 
 exports.Variable = Variable;
+exports.VariableRef = VariableRef;
 exports.ExportedVariable = ExportedVariable;
 exports.Module = Module;
 exports.Type = Type;

+ 1 - 1
test/expected/cast.js

@@ -47,7 +47,7 @@ var pd1 = null;
 var pd2 = null;
 var pad = null;
 
-function p(b/*Base*/, d1/*Derived1*/){
+function p(b/*VAR Base*/, d1/*VAR Derived1*/){
 	RTL$.typeGuard(b, Derived1).field1 = 0;
 	RTL$.typeGuard(b, Derived2).field2 = 1;
 	RTL$.typeGuard(d1, Derived2).field2 = 2;

+ 1 - 1
test/input/cast.ob

@@ -15,7 +15,7 @@ VAR
 	pd2: POINTER TO Derived2;
     pad: PAnonymousDerived;
 
-PROCEDURE p(b: Base; d1: Derived1);
+PROCEDURE p(VAR b: Base; VAR d1: Derived1);
 BEGIN
     b(Derived1).field1 := 0;
     b(Derived2).field2 := 1;

+ 22 - 4
test/test_unit.js

@@ -37,7 +37,8 @@ function runAndHandleErrors(action, s, handlerError){
     }
     catch (x){
         if (!(x instanceof Errors.Error))
-            throw new Error("'" + s + '":\n' + x.stack);
+            throw new Error("'" + s + "': " + x + "\n" 
+                            + (x.stack ? x.stack : "(no stack)"));
         
         if (handlerError)
             handlerError(x);
@@ -370,10 +371,11 @@ var testSuite = {
            "type mismatch: 'pDerived' is 'POINTER TO Derived' and cannot be assigned to 'POINTER TO Base' expression"],
           ["NIL := p1", "not parsed"])
     ),
-"POINTER cast": testWithContext(
+"typeguard": testWithContext(
     context(Grammar.expression,
             "TYPE Base = RECORD END; PBase = POINTER TO Base; Derived = RECORD (Base) END; PDerived = POINTER TO Derived;"
-            + "VAR p1, p2: POINTER TO RECORD END; pBase: POINTER TO Base; pDerived: POINTER TO Derived; i: INTEGER;"),
+            + "VAR p1, p2: POINTER TO RECORD END; pBase: POINTER TO Base; pDerived: POINTER TO Derived;"
+            + "vb: Base; i: INTEGER;"),
     pass("pBase(PDerived)",
          "pBase^(Derived)"),
     fail(["pDerived(PDerived)",
@@ -383,7 +385,23 @@ var testSuite = {
          ["p1(INTEGER)", 
           "invalid type cast: POINTER type expected as an argument of POINTER type guard, got 'INTEGER'"],
          ["i(Derived)",
-          "invalid type cast: 'Derived' is not an extension of 'INTEGER'"])
+          "invalid type cast: 'Derived' is not an extension of 'INTEGER'"],
+         ["vb(Derived)",
+          "invalid type cast: a value variable and cannot be used in typeguard"],
+         ["vb(PDerived)",
+          "invalid type cast: a value variable and cannot be used in typeguard"])
+    ),
+"typeguard for VAR argument": testWithContext(
+    context(Grammar.procedureDeclaration,
+            "TYPE Base = RECORD END; Derived = RECORD (Base) i: INTEGER END;"
+            + "T = RECORD END; TD = RECORD(T) b: Base END;"),
+    pass("PROCEDURE proc(VAR p: Base); BEGIN p(Derived).i := 1; END proc"),
+    fail(["PROCEDURE proc(p: Base); BEGIN p(Derived).i := 1; END proc",
+          "invalid type cast: a value variable and cannot be used in typeguard"],
+         ["PROCEDURE proc(p: TD); BEGIN p.b(Derived).i := 1; END proc",
+          "invalid type cast: a value variable and cannot be used in typeguard"],
+         ["PROCEDURE proc(VAR p: T); BEGIN p(TD).b(Derived).i := 1; END proc",
+          "invalid type cast: a value variable and cannot be used in typeguard"])
     ),
 "POINTER relations": testWithContext(
     context(Grammar.expression,