From 1debdf7cc7e94ca39833521d1d8a3d6bde5d9347 Mon Sep 17 00:00:00 2001
From: "Adam D. Ruppe" <destructionator@gmail.com>
Date: Sat, 15 Jan 2022 13:22:34 -0500
Subject: [PATCH] fix more property wrapping stuff

---
 jsvar.d  | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 script.d | 29 +++++++++++++++------------
 2 files changed, 72 insertions(+), 17 deletions(-)

diff --git a/jsvar.d b/jsvar.d
index edb56df..fbb1dc8 100644
--- a/jsvar.d
+++ b/jsvar.d
@@ -808,7 +808,14 @@ struct var {
 
 	public var opOpAssign(string op, T)(T t) {
 		if(payloadType() == Type.Object) {
-			if(this._payload._object !is null) {
+			if(auto pt = cast(PropertyPrototype) this._payload._object) {
+				auto propValue = pt.get;
+				auto result = propValue.opOpAssign!(op)(t);
+
+				pt.set(result);
+
+				return result;
+			} else if(this._payload._object !is null) {
 				var* operator = this._payload._object._peekMember("opOpAssign", true);
 				if(operator !is null && operator._type == Type.Function)
 					return operator.call(this, op, t);
@@ -1375,7 +1382,7 @@ struct var {
 			else
 				return *(new var);
 		}
-		return from._getMember(name, true, false, file, line);
+		return from._getMember(name, true, false, false, file, line);
 	}
 
 	public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) {
@@ -1823,12 +1830,14 @@ class PrototypeObject {
 	}
 
 	// FIXME: maybe throw something else
-	/*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) {
+	/*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, bool returnRawProperty = false, string file = __FILE__, size_t line = __LINE__) {
 		var* mem = _peekMember(name, recurse);
 
 		if(mem !is null) {
 			// If it is a property, we need to call the getter on it
 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
+				if(returnRawProperty)
+					return *mem;
 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
 				return prop.get;
 			}
@@ -1854,14 +1863,25 @@ class PrototypeObject {
 	/*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) {
 		var* mem = _peekMember(name, recurse);
 
+		bool onParent = false;
+
+		if(mem is null && !recurse) {
+			mem = _peekMember(name, true); // properties need the check anyway as a setter might be on a prototype
+			onParent = true;
+		}
+
 		if(mem !is null) {
 			// Property check - the setter should be proxied over to it
 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
 				return prop.set(t);
 			}
-			*mem = t;
-			return *mem;
+			if(!onParent) {
+				*mem = t;
+				return *mem;
+			} else {
+				mem = null;
+			}
 		}
 
 		if(!suppressOverloading) {
@@ -2369,6 +2389,36 @@ unittest {
 	);
 }
 
+version(with_arsd_script)
+unittest {
+	import arsd.script;
+	static class Test {
+		@scriptable int a;
+	}
+
+	Test test = new Test;
+
+	test.a = 15;
+
+	var globals = var.emptyObject;
+	globals.test = test;
+	globals.Test = subclassable!Test;
+
+	interpret(q{
+		  test.a = 4;
+		  var wtf = test.a += 5;
+		  assert(wtf == 9);
+		  assert(test.a == 9);
+		  var test2 = new Test;
+		  test2.a = 2;
+		  test2.a += 5;
+		  assert(test2.a == 7);
+	}, globals);
+
+	assert(test.a == 9);
+	assert(globals.test2.get!Test.a == 7);
+}
+
 // just a base class we can reference when looking for native objects
 class WrappedNativeObject : PrototypeObject {
 	TypeInfo wrappedType;
diff --git a/script.d b/script.d
index 7764c82..13d7305 100644
--- a/script.d
+++ b/script.d
@@ -1503,7 +1503,7 @@ class OpAssignExpression : Expression {
 		var n;
 		foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=", "%="))
 			if(ctOp[0..1] == op)
-				n = mixin("v.getVar(sc) "~ctOp~" right");
+				n = mixin("v.getVar(sc, true, true) "~ctOp~" right");
 
 		// FIXME: ensure the variable is updated in scope too
 
@@ -1579,9 +1579,9 @@ class VariableExpression : Expression {
 		return getVar(sc).get!string;
 	}
 
-	ref var getVar(PrototypeObject sc, bool recurse = true) {
+	ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) {
 		try {
-			return sc._getMember(identifier, true /* FIXME: recurse?? */, true);
+			return sc._getMember(identifier, true /* FIXME: recurse?? */, true, returnRawProperty);
 		} catch(DynamicTypeException dte) {
 			dte.callStack ~= loc;
 			throw dte;
@@ -1592,7 +1592,12 @@ class VariableExpression : Expression {
 		return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading);
 	}
 
-	ref var getVarFrom(PrototypeObject sc, ref var v) {
+	ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) {
+		if(returnRawProperty) {
+			if(v.payloadType == var.Type.Object)
+				return v._payload._object._getMember(identifier, true, false, returnRawProperty);
+		}
+
 		return v[identifier];
 	}
 
@@ -1658,7 +1663,7 @@ class DotVarExpression : VariableExpression {
 		return e1.toString() ~ "." ~ e2.toString();
 	}
 
-	override ref var getVar(PrototypeObject sc, bool recurse = true) {
+	override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) {
 		if(!this.recurse) {
 			// this is a special hack...
 			if(auto ve = cast(VariableExpression) e1) {
@@ -1676,7 +1681,7 @@ class DotVarExpression : VariableExpression {
 		}
 
 		if(auto ve = cast(VariableExpression) e1) {
-			return this.getVarFrom(sc, ve.getVar(sc, recurse));
+			return this.getVarFrom(sc, ve.getVar(sc, recurse), returnRawProperty);
 		} else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") {
 			auto se = cast(StringLiteralExpression) e1;
 			var* functor = new var;
@@ -1690,7 +1695,7 @@ class DotVarExpression : VariableExpression {
 			// make a temporary for the lhs
 			auto v = new var();
 			*v = e1.interpret(sc).value;
-			return this.getVarFrom(sc, *v);
+			return this.getVarFrom(sc, *v, returnRawProperty);
 		}
 	}
 
@@ -1702,8 +1707,8 @@ class DotVarExpression : VariableExpression {
 	}
 
 
-	override ref var getVarFrom(PrototypeObject sc, ref var v) {
-		return e2.getVarFrom(sc, v);
+	override ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) {
+		return e2.getVarFrom(sc, v, returnRawProperty);
 	}
 
 	override string toInterpretedString(PrototypeObject sc) {
@@ -1725,13 +1730,13 @@ class IndexExpression : VariableExpression {
 		return e1.toString() ~ "[" ~ e2.toString() ~ "]";
 	}
 
-	override ref var getVar(PrototypeObject sc, bool recurse = true) {
+	override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) {
 		if(auto ve = cast(VariableExpression) e1)
-			return ve.getVar(sc, recurse)[e2.interpret(sc).value];
+			return ve.getVar(sc, recurse, returnRawProperty)[e2.interpret(sc).value];
 		else {
 			auto v = new var();
 			*v = e1.interpret(sc).value;
-			return this.getVarFrom(sc, *v);
+			return this.getVarFrom(sc, *v, returnRawProperty);
 		}
 	}