diff --git a/src/dmd/expressionsem.d b/src/dmd/expressionsem.d index 70c05546ebc6..3a2c2585f478 100644 --- a/src/dmd/expressionsem.d +++ b/src/dmd/expressionsem.d @@ -9430,6 +9430,221 @@ Expression binSemanticProp(BinExp e, Scope* sc) return null; } +/******************************************************************************** + * Helper function to resolve `@property` functions in a `BinAssignExp`. + * It rewrites expressions of the form `e1.prop @= e2` to `e1.x(e1.x @ e2)` + * if 'e1` is a type, otherwise it rewrites to + * `((auto ref _e1) => _e1.prop(_e1.prop @ e2))(e1)` + * Params: + * e = the binary assignment expression to rewrite + * sc = the semantic scope + * Returns: + * the rewritten expression if the procedure succeeds, an `ErrorExp` if the + * and error is encountered, or `null` if `e.e1` is not a `@property` function. + */ +Expression binSemanticProp(BinAssignExp e, Scope* sc) +{ + import dmd.statement; + + // This will convert id expressions to var expressions + Expression e1x = e.e1.expressionSemantic(sc); + Expression e2x = e.e2.expressionSemantic(sc); + + if (e1x.op == TOK.error) + return e1x; + if (e2x.op == TOK.error) + return e2x; + + // This will convert a var expression to a call expression + e1x = resolveProperties(sc, e1x); + e2x = resolveProperties(sc, e2x); + if (e1x.op == TOK.error) + return e1x; + if (e2x.op == TOK.error) + return e2x; + + // Check for property assignment. + // https://issues.dlang.org/show_bug.cgi?id=8006 + if (e1x.op == TOK.call) + { + // Only rewrite @property functions that are not lvalues + auto e1_call = cast(CallExp)e1x; + auto tf = (e1_call.e1.type.ty) == Tfunction ? cast(TypeFunction)e1_call.e1.type : null; + if (tf && tf.isproperty && !e1_call.isLvalue) + { + // Need to rewrite e1.prop @= e2 + // if e1 is a type (e.g. static @property functions) then rewrite to + // e1.x(e1.x() @ e2) + // otherwise rewrite to + // ((auto ref _e1) => _e1.prop(_e1.prop() @ e2))(e1) + + // We need to get e1. + Expression e1 = e.e1.copy(); + + bool noLambda = false; + if (e1.op == TOK.dotIdentifier) + { + auto e1_dotId = cast(DotIdExp)e1; + noLambda = e1_dotId.e1.op == TOK.type; + } + else if (e1.op == TOK.identifier) + { + noLambda = true; + } + + // create expression `_e1.prop() @ e2` + Expression createOperation(Expression _e1PropCall) + { + Expression expOp = null; + switch(e.op) + { + case TOK.concatenateAssign: + expOp = new CatExp(e.loc, _e1PropCall, e2x); + break; + case TOK.addAssign: + expOp = new AddExp(e.loc, _e1PropCall, e2x); + break; + case TOK.minAssign: + expOp = new MinExp(e.loc, _e1PropCall, e2x); + break; + case TOK.mulAssign: + expOp = new MulExp(e.loc, _e1PropCall, e2x); + break; + case TOK.divAssign: + expOp = new DivExp(e.loc, _e1PropCall, e2x); + break; + case TOK.modAssign: + expOp = new ModExp(e.loc, _e1PropCall, e2x); + break; + case TOK.powAssign: + expOp = new PowExp(e.loc, _e1PropCall, e2x); + break; + case TOK.andAssign: + expOp = new AndExp(e.loc, _e1PropCall, e2x); + break; + case TOK.orAssign: + expOp = new OrExp(e.loc, _e1PropCall, e2x); + break; + case TOK.xorAssign: + expOp = new XorExp(e.loc, _e1PropCall, e2x); + break; + case TOK.leftShiftAssign: + expOp = new ShlExp(e.loc, _e1PropCall, e2x); + break; + case TOK.rightShiftAssign: + expOp = new ShrExp(e.loc, _e1PropCall, e2x); + break; + case TOK.unsignedRightShiftAssign: + expOp = new UshrExp(e.loc, _e1PropCall, e2x); + break; + default: + assert(false); // operator was not handled + } + + return expOp; + } + + Expression result = null; + + // e.g. nested @property function, module-level function, or e1 is a type + if (noLambda) + { + // Create expression `e1.prop()` or `prop()` + auto getterCall = new CallExp(e.loc, e1); + + // Create expression `e1.prop() @ e2` or `prop() @ e2` + auto e1DotProp_op_e2 = createOperation(getterCall); + + // create expression `e1.prop(e1.prop() @ e2)` or `prop(prop() @ e2)` + auto setterCall = new CallExp(e.loc, e1.copy(), e1DotProp_op_e2); + + result = setterCall.expressionSemantic(sc); + } + else + { + // Create expression `_e1.prop` + auto _e1 = new Identifier("_e1"); + Expression _e1DotProp = null; + if (e1.op == TOK.dotIdentifier) + { + auto e1_dotId = cast(DotIdExp)e1; + auto _e1Id = new IdentifierExp(e.loc, _e1); + _e1DotProp = new DotIdExp(e.loc, _e1Id, e1_dotId.ident); + } + else + { + assert(false); // Expression was not handled + } + + // Create expression `_e1.prop()` + auto getterCall = new CallExp(e.loc, _e1DotProp); + + // Create expression `_e1.prop() @ e2` + auto _e1DotProp_op_e2 = createOperation(getterCall); + + // create expression `_e1.prop(_e1.prop() @ e2)` + auto setterCall = new CallExp(e.loc, _e1DotProp, _e1DotProp_op_e2); + + // wrap setter in lambda expression + // ******************************** + + auto idType = Identifier.generateId("__T"); + auto idparamType = new TypeIdentifier(e.loc, idType); + auto params = new Parameters(); + auto param = new Parameter(STC.auto_ | STC.ref_, idparamType, _e1, null); + params.push(param); + + // need to wrap in a template declaration or compiler throws unknown identifier + // error for idType + auto tplParams = new TemplateParameters(); + tplParams.push(new TemplateTypeParameter(e.loc, idType, null, null)); + + auto typeFunc = new TypeFunction(params, null, 0, LINK.default_); + auto fd = new FuncLiteralDeclaration(e.loc, e.loc, typeFunc, TOK.reserved, null); + fd.fbody = new ReturnStatement(e.loc, setterCall); + + auto declSyms = new Dsymbols(); + declSyms.push(fd); + auto td = new TemplateDeclaration(fd.loc, fd.ident, tplParams, null, declSyms, false, true); + + auto expFunc = new FuncExp(e.loc, td); + + // Create parameter `e1` for lambda expression + auto exps = new Expressions(); + if (e1.op == TOK.dotIdentifier) + { + auto expDotId = cast(DotIdExp)e1; + exps.push(expDotId.e1); + } + else if (e1.op == TOK.dotVariable) + { + auto expDotVar = cast(DotVarExp)e1; + exps.push(expDotVar.e1); + } + else + { + assert(false); // expression type not handled + } + + // create expression ((auto ref _e1) => _e1.prop(_e1.prop() @ e2))(e1) + auto lambdaCall = new CallExp(e.loc, expFunc, exps); + + result = lambdaCall.expressionSemantic(sc); + } + + // if result is null, we still need to set e.e1 and e.e2 at the end of this function + if (result) + { + return result; + } + } + } + + e.e1 = e1x; + e.e2 = e2x; + return null; +} + // entrypoint for semantic ExpressionSemanticVisitor extern (C++) Expression expressionSemantic(Expression e, Scope* sc) { diff --git a/test/fail_compilation/test8006.d b/test/fail_compilation/test8006.d new file mode 100644 index 000000000000..2174e7b2a6c2 --- /dev/null +++ b/test/fail_compilation/test8006.d @@ -0,0 +1,192 @@ +/* + * TEST_OUTPUT: +--- +fail_compilation/test8006.d(79): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(80): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(81): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(82): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(83): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(84): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(85): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(86): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(87): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(88): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(89): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(90): Error: function `test8006.TInt.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(94): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(95): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(96): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(97): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(98): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(99): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(100): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(101): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(102): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(103): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(104): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(105): Error: `ti.y()` is not an lvalue +fail_compilation/test8006.d(124): Error: function `test8006.TString.x()` is not callable using argument types `(string)` +fail_compilation/test8006.d(128): Error: `ts.y()` is not an lvalue +fail_compilation/test8006.d(154): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(155): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(156): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(157): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(158): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(159): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(160): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(161): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(162): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(163): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(164): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(165): Error: function `test8006.x()` is not callable using argument types `(int)` +fail_compilation/test8006.d(167): Error: function `test8006.xs()` is not callable using argument types `(string)` +fail_compilation/test8006.d(171): Error: `y()` is not an lvalue +fail_compilation/test8006.d(172): Error: `y()` is not an lvalue +fail_compilation/test8006.d(173): Error: `y()` is not an lvalue +fail_compilation/test8006.d(174): Error: `y()` is not an lvalue +fail_compilation/test8006.d(175): Error: `y()` is not an lvalue +fail_compilation/test8006.d(176): Error: `y()` is not an lvalue +fail_compilation/test8006.d(177): Error: `y()` is not an lvalue +fail_compilation/test8006.d(178): Error: `y()` is not an lvalue +fail_compilation/test8006.d(179): Error: `y()` is not an lvalue +fail_compilation/test8006.d(180): Error: `y()` is not an lvalue +fail_compilation/test8006.d(181): Error: `y()` is not an lvalue +fail_compilation/test8006.d(182): Error: `y()` is not an lvalue +fail_compilation/test8006.d(184): Error: `ys()` is not an lvalue +--- + */ + +// https://issues.dlang.org/show_bug.cgi?id=8006 + +// modeled after code from runnable/testassign.d + +struct TInt +{ + int mX; + + @property int x() { return mX; } + + int mY; + int y() { return mY; } + int y(int v) { return mY = v; } +} + +void testTInt() +{ + // all of these should fail to compile because there is + // no setter property + TInt ti; + ti.x += 4; + ti.x -= 2; + ti.x *= 4; + ti.x /= 2; + ti.x %= 3; + ti.x <<= 3; + ti.x >>= 1; + ti.x >>>= 1; + ti.x &= 0xF; + ti.x |= 0x8; + ti.x ^= 0xF; + ti.x ^^= 2; + + // all of these should fail to compile because y is not a + // @property function + ti.y += 4; + ti.y -= 2; + ti.y *= 4; + ti.y /= 2; + ti.y %= 3; + ti.y <<= 3; + ti.y >>= 1; + ti.y >>>= 1; + ti.y &= 0xF; + ti.y |= 0x8; + ti.y ^= 0xF; + ti.y ^^= 2; +} + +struct TString +{ + string mX; + + @property string x() { return mX; } + + string mY; + string y() { return mY; } + string y(string v) { return mY = v; } +} + +void testTString() +{ + // this should fail to compile because there is + // no setter property + TString ts; + ts.x ~= "def"; + + // this should fail to compile because y is not a + // @property function + ts.y ~= "def"; +} + + +// int @property function without a setter +int mX; +@property int x() { return mX; } + +// int non-@property functions +int mY; +int y() { return mY; } +int y(int v) { return mY = v; } + +// string @property function without a setter +string mXs; +@property string xs() { return mXs; } + +// string non-@property functions +string mYs; +string ys() { return mYs; } +string ys(string v) { return mYs = v; } + +void testFreeFunctions() +{ + // all of these should fail to compile because there is + // no setter property + x += 4; + x -= 2; + x *= 4; + x /= 2; + x %= 3; + x <<= 3; + x >>= 1; + x >>>= 1; + x &= 0xF; + x |= 0x8; + x ^= 0xF; + x ^^= 2; + + xs ~= "def"; + + // all of these should fail to compile because y and ys are not + // @property function + y += 4; + y -= 2; + y *= 4; + y /= 2; + y %= 3; + y <<= 3; + y >>= 1; + y >>>= 1; + y &= 0xF; + y |= 0x8; + y ^= 0xF; + y ^^= 2; + + ys ~= "def"; +} + +void main() +{ + testTInt(); + testTString(); + testFreeFunctions(); +} diff --git a/test/runnable/test17684.d b/test/runnable/test17684.d index c83d71681765..3e3a60258c65 100644 --- a/test/runnable/test17684.d +++ b/test/runnable/test17684.d @@ -101,19 +101,19 @@ int intTest(T)() assert(t <= 42); assert(42 >= t); + t += 1; + assert(t == 43); + + t -= 1; + assert(t == 42); + // These currently don't work for properties due to https://issues.dlang.org/show_bug.cgi?id=8006 static if (!(typeid(T) is typeid(StructProperty!int)) && !(typeid(T) is typeid(ClassProperty!int))) { t++; // test a few unary and binary operators assert(t == 43); - t += 1; - assert(t == 44); - t--; - assert(t == 43); - - t -= 1; assert(t == 42); } diff --git a/test/runnable/test8006.d b/test/runnable/test8006.d new file mode 100644 index 000000000000..2b1b9fe1ae3a --- /dev/null +++ b/test/runnable/test8006.d @@ -0,0 +1,679 @@ +// https://issues.dlang.org/show_bug.cgi?id=8006 + +/************************************************************** +* int tests +**************************************************************/ +struct TInt +{ + int mX; + + @property int x() { return mX; } + @property void x(int v) { mX = v; } + + alias x this; +} + +// It was found, during the implementation of binary assignment operators +// for @property functions that if the setter was declared before the getter +// the binary assignment operator call would not compile. This was due +// to the fact that if e.e1.copy() was called after resolveProperties(e.e1) +// that the copy() call would return the wrong overload for the @property +// function. This is a test to guard against that. +struct TIntRev +{ + int mX; + + @property void x(int v) { mX = v; } + @property int x() { return mX; } + + alias x this; +} + +// Ensure that `ref @property` functions also *still* work +struct TIntRef +{ + int mX; + + @property ref int x() { return mX; } + @property ref int x(int v) { return mX = v; } + + alias x this; +} + +// same as above with no setter +struct TIntRefNoSetter +{ + int mX; + + @property ref int x() { return mX; } + + alias x this; +} + +// Same as TInt, but setter @property function returns a value +struct TIntRet +{ + int mX; + + @property int x() { return mX; } + @property int x(int v) { return mX = v; } + + alias x this; +} + +// same as TInt, but with static @property functions +struct TIntStatic +{ + static int mX; + + static @property int x() { return mX; } + static @property void x(int v) { mX = v; } + + alias x this; +} + +// same as TIntStatic, but setter @property function returns a value +struct TIntRetStatic +{ + static int mX; + + static @property int x() { return mX; } + static @property int x(int v) { return mX = v; } + + alias x this; +} + +// This test verifies typical arithmetic and logical operators +void testTInt(T)() +{ + // modeled after code from runnable/testassign.d + + static if (typeid(T) is typeid(TInt)) + { + TInt t; + } + else static if (typeid(T) is typeid(TIntRev)) + { + TIntRev t; + } + else static if (typeid(T) is typeid(TIntRef)) + { + TIntRef t; + } + else static if (typeid(T) is typeid(TIntRefNoSetter)) + { + TIntRefNoSetter t; + } + else static if (typeid(T) is typeid(TIntStatic)) + { + alias t = TIntStatic; + } + else + { + static assert(false, "Type is not supported"); + } + + t.x += 4; + assert(t.mX == 4); + t.x -= 2; + assert(t.mX == 2); + t.x *= 4; + assert(t.mX == 8); + t.x /= 2; + assert(t.mX == 4); + t.x %= 3; + assert(t.mX == 1); + t.x <<= 3; + assert(t.mX == 8); + t.x >>= 1; + assert(t.mX == 4); + t.x >>>= 1; + assert(t.mX == 2); + t.x &= 0xF; + assert(t.mX == 0x2); + t.x |= 0x8; + assert(t.mX == 0xA); + t.x ^= 0xF; + assert(t.mX == 0x5); + t.x ^^= 2; + assert(t.mX == 25); + + // same as test above, but through the `alias this` + t = 0; + t += 4; + assert(t.mX == 4); + t -= 2; + assert(t.mX == 2); + t *= 4; + assert(t.mX == 8); + t /= 2; + assert(t.mX == 4); + t %= 3; + assert(t.mX == 1); + t <<= 3; + assert(t.mX == 8); + t >>= 1; + assert(t.mX == 4); + t >>>= 1; + assert(t.mX == 2); + t &= 0xF; + assert(t.mX == 0x2); + t |= 0x8; + assert(t.mX == 0xA); + t ^= 0xF; + assert(t.mX == 0x5); + t ^^= 2; + assert(t.mX == 25); +} + +// This test is to verify that the setter @property function +// returns a value if it is explicitly coded to do so +void testTIntRet(T)() +{ + static if (typeid(T) is typeid(TIntRet)) + { + TIntRet t; + } + else static if (typeid(T) is typeid(TIntRetStatic)) + { + alias t = TIntRetStatic; + } + else + { + static assert(false, "Type is not supported"); + } + + int r; + r = t.x += 4; + assert(r == 4); + r = t.x -= 2; + assert(r == 2); + r = t.x *= 4; + assert(r == 8); + r = t.x /= 2; + assert(r == 4); + r = t.x %= 3; + assert(r == 1); + r = t.x <<= 3; + assert(r == 8); + r = t.x >>= 1; + assert(r == 4); + r = t.x >>>= 1; + assert(r == 2); + r = t.x &= 0xF; + assert(r == 0x2); + r = t.x |= 0x8; + assert(r == 0xA); + r = t.x ^= 0xF; + assert(r == 0x5); + r = t.x ^^= 2; + assert(r == 25); + + // same as test above, but through the `alias this` + t = 0; + r = t += 4; + assert(r == 4); + r = t -= 2; + assert(r == 2); + r = t *= 4; + assert(r == 8); + r = t /= 2; + assert(r == 4); + r = t %= 3; + assert(r == 1); + r = t <<= 3; + assert(r == 8); + r = t >>= 1; + assert(r == 4); + r = t >>>= 1; + assert(r == 2); + r = t &= 0xF; + assert(r == 0x2); + r = t |= 0x8; + assert(r == 0xA); + r = t ^= 0xF; + assert(r == 0x5); + r = t ^^= 2; + assert(r == 25); +} + +/************************************************************** +* string/array tests +**************************************************************/ +struct TString +{ + string mX; + + @property string x() { return mX; } + @property void x(string v) { mX = v; } + + alias x this; +} + +// same as TString, but setter @property function returns a value +struct TStringRet +{ + string mX; + + @property string x() { return mX; } + @property string x(string v) { return mX = v; } + + alias x this; +} + +struct TStringOp +{ + string mX; + + @property string x() { return mX; } + @property void x(string v) { mX = v; } + + string mB; + string opOpAssign(string op)(string rhs) + { + return mixin("mB "~op~"= rhs"); + } + + alias x this; +} + +// same as TString, but for static @property functions +struct TStringStatic +{ + static string mX; + + static @property string x() { return mX; } + static @property void x(string v) { mX = v; } + + static alias x this; +} + +// same as TStringRet, but for static @property functions +struct TStringRetStatic +{ + static string mX; + + static @property string x() { return mX; } + static @property string x(string v) { return mX = v; } + + static alias x this; +} + +// Test string (i.e. array) operators +void testTString(T)() +{ + static if (typeid(T) is typeid(TString)) + { + TString t; + } + else static if (typeid(T) is typeid(TStringStatic)) + { + alias t = TStringStatic; + } + else + { + static assert(false, "Type is not supported"); + } + + t.x = "abc"; + t.x ~= "def"; + assert(t.mX == "abcdef"); + + // same as test above, but through the `alias this` + t = "abc"; + t ~= "def"; + assert(t.mX == "abcdef"); +} + +// This test is to verify that the setter @property function +// returns a value if it is explicitly coded to do so +void testTStringRet(T)() +{ + static if (typeid(T) is typeid(TStringRet)) + { + TStringRet t; + } + else static if (typeid(T) is typeid(TStringRetStatic)) + { + alias t = TStringRetStatic; + } + else + { + static assert(false, "Type is not supported"); + } + + string s; + t.x = "abc"; + s = t.x ~= "def"; + assert(s == "abcdef"); + + // same as test above, but through the `alias this` + t = "abc"; + s = t ~= "def"; + assert(s == "abcdef"); +} + +/************************************************************** +* Free @property function test +**************************************************************/ +int mX; +@property int x() { return mX; } +@property void x(int v) { mX = v; } + +// Test that free @property functions work +void testFreeFunctionsInt() +{ + x += 4; + assert(mX == 4); + x -= 2; + assert(mX == 2); + x *= 4; + assert(mX == 8); + x /= 2; + assert(mX == 4); + x %= 3; + assert(mX == 1); + x <<= 3; + assert(mX == 8); + x >>= 1; + assert(mX == 4); + x >>>= 1; + assert(mX == 2); + x &= 0xF; + assert(mX == 0x2); + x |= 0x8; + assert(mX == 0xA); + x ^= 0xF; + assert(mX == 0x5); + x ^^= 2; + assert(mX == 25); +} + +int mXret; +@property int xret() { return mXret; } +@property int xret(int v) { return mXret = v; } + +// Same as testFreeFunctions except that we want to +// ensure the binary assignment returns a value +void testFreeFunctionsIntRet() +{ + int r; + r = xret += 4; + assert(r == 4); + r = xret -= 2; + assert(r == 2); + r = xret *= 4; + assert(r == 8); + r = xret /= 2; + assert(r == 4); + r = xret %= 3; + assert(r == 1); + r = xret <<= 3; + assert(r == 8); + r = xret >>= 1; + assert(r == 4); + r = xret >>>= 1; + assert(r == 2); + r = xret &= 0xF; + assert(r == 0x2); + r = xret |= 0x8; + assert(r == 0xA); + r = xret ^= 0xF; + assert(r == 0x5); + r = xret ^^= 2; + assert(r == 25); +} + +string mXs; +@property string xs() { return mXs; } +@property void xs(string v) { mXs = v; } + +void testFreeFunctionsString() +{ + xs = "abc"; + xs ~= "def"; + assert(mXs == "abcdef"); +} + +string mXsret; +@property string xsret() { return mXsret; } +@property string xsret(string v) { return mXsret = v; } + +void testFreeFunctionsStringRet() +{ + string s; + xsret = "abc"; + s = xsret ~= "def"; + assert(s == "abcdef"); +} + +/************************************************************** +* Nested @property function test +**************************************************************/ + +// At the time of writing this test case, the compiler would not +// allow overloading nested functions. So, it was impossible to +// create a getter/setter pair, but it wouldd allow a single ref +// @property getter, so I added that test. + +// Test that nested @property functions work +void testNestedFunctionsIntRef() +{ + int mP; + @property ref int p() { return mP; } + + p += 4; + assert(mP == 4); + p -= 2; + assert(mP == 2); + p *= 4; + assert(mP == 8); + p /= 2; + assert(mP == 4); + p %= 3; + assert(mP == 1); + p <<= 3; + assert(mP == 8); + p >>= 1; + assert(mP == 4); + p >>>= 1; + assert(mP == 2); + p &= 0xF; + assert(mP == 0x2); + p |= 0x8; + assert(mP == 0xA); + p ^= 0xF; + assert(mP == 0x5); + p ^^= 2; + assert(mP == 25); +} + +// Same as testNestedFunctionsIntRef except that we want to +// ensure the binary assignment returns a value +void testNestedFunctionsIntRefRet() +{ + int mP; + @property ref int p() { return mP; } + + int r; + r = p += 4; + assert(r == 4); + r = p -= 2; + assert(r == 2); + r = p *= 4; + assert(r == 8); + r = p /= 2; + assert(r == 4); + r = p %= 3; + assert(r == 1); + r = p <<= 3; + assert(r == 8); + r = p >>= 1; + assert(r == 4); + r = p >>>= 1; + assert(r == 2); + r = p &= 0xF; + assert(r == 0x2); + r = p |= 0x8; + assert(r == 0xA); + r = p ^= 0xF; + assert(r == 0x5); + r = p ^^= 2; + assert(r == 25); +} + +void testNestedFunctionsStringRef() +{ + string mP; + @property ref string p() { return mP; } + + mP = "abc"; + p ~= "def"; + assert(mP == "abcdef"); +} + +// Same as testNestedFunctionsStringRef except that we want to +// ensure the binary assignment returns a value +void testNestedFunctionsStringRefRet() +{ + string mP; + @property ref string p() { return mP; } + + string s; + mP = "abc"; + s = p ~= "def"; + assert(s == "abcdef"); +} + +/************************************************************** +* This test is to ensure the that expression e1.prop @= e2 is +* rewritten in a way that does not evaluate e1 more than once +**************************************************************/ +struct TSideEffectsInt +{ + int mX; + @property int X() {return mX;} + @property int X(int value) { return mX = value;} +} + +TSideEffectsInt tSideEffectsInt; +int tSideEffectsIntCount = 0; + +TSideEffectsInt* getTSideEffectsInt() +{ + tSideEffectsIntCount++; + return &tSideEffectsInt; +} + +void testSideEffectsInt() +{ + tSideEffectsInt.mX = 0; + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X += 4; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X -= 2; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X *= 4; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X /= 2; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X %= 3; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X <<= 3; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X >>= 1; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X >>>= 1; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X &= 0xF; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X |= 0x8; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X ^= 0xF; + assert(tSideEffectsIntCount == 1); + + tSideEffectsIntCount = 0; + getTSideEffectsInt().X ^^= 2; + assert(tSideEffectsIntCount == 1); +} + +// Same as testSideEffectsInt() only for string type +struct TSideEffectsString +{ + string mX; + @property string X() {return mX;} + @property string X(string value) { return mX = value;} +} + +TSideEffectsString tSideEffectsString; +int tSideEffectsStringCount = 0; + +TSideEffectsString* getTSideEffectsString() +{ + tSideEffectsStringCount++; + return &tSideEffectsString; +} + +void testSideEffectsString() +{ + tSideEffectsString.mX = "abc"; + + tSideEffectsStringCount = 0; + getTSideEffectsString().X ~= "def"; + assert(tSideEffectsStringCount == 1); +} + +void main() +{ + testTInt!TInt(); + testTInt!TIntRev(); + testTInt!TIntRef(); + testTInt!TIntRefNoSetter(); + testTInt!TIntStatic(); + + testTIntRet!TIntRet(); + testTIntRet!TIntRetStatic(); + + testTString!TString(); + testTString!TStringStatic(); + + testTStringRet!TStringRet(); + testTStringRet!TStringRetStatic(); + + testFreeFunctionsInt(); + testFreeFunctionsIntRet(); + + testFreeFunctionsString(); + testFreeFunctionsStringRet(); + + testNestedFunctionsIntRef(); + testNestedFunctionsIntRefRet(); + + testNestedFunctionsStringRef(); + testNestedFunctionsStringRefRet(); + + testSideEffectsInt(); + + testSideEffectsString(); +}