From bc64f7adaf2709fa01825826da732787d42cf27e Mon Sep 17 00:00:00 2001 From: Walter Bright Date: Sat, 2 Nov 2024 22:55:53 -0700 Subject: [PATCH] add __rvalue(expression) builtin --- .gitignore | 5 +- compiler/src/dmd/astbase.d | 1 + compiler/src/dmd/clone.d | 16 ++-- compiler/src/dmd/declaration.h | 1 + compiler/src/dmd/dscope.d | 2 + compiler/src/dmd/dstruct.d | 8 +- compiler/src/dmd/dsymbolsem.d | 22 +++-- compiler/src/dmd/e2ir.d | 2 + compiler/src/dmd/expression.d | 39 +++++---- compiler/src/dmd/expression.h | 1 + compiler/src/dmd/expressionsem.d | 33 ++++++-- compiler/src/dmd/frontend.h | 79 ++++++++++-------- compiler/src/dmd/func.d | 6 +- compiler/src/dmd/funcsem.d | 2 +- compiler/src/dmd/globals.h | 2 +- compiler/src/dmd/hdrgen.d | 6 ++ compiler/src/dmd/mtype.d | 2 +- compiler/src/dmd/parse.d | 10 +++ compiler/src/dmd/scope.h | 1 + compiler/src/dmd/templatesem.d | 10 +-- compiler/src/dmd/tokens.d | 7 ++ compiler/src/dmd/tokens.h | 1 + compiler/src/dmd/typesem.d | 63 ++++++++++---- compiler/test/fail_compilation/fail19871.d | 20 ----- compiler/test/fail_compilation/fail19931.d | 15 ---- compiler/test/fail_compilation/fail23036.d | 22 ----- compiler/test/runnable/rvalue1.d | 96 ++++++++++++++++++++++ compiler/test/runnable/test23036.d | 13 +++ compiler/test/unit/lexer/location_offset.d | 1 + 29 files changed, 336 insertions(+), 150 deletions(-) delete mode 100644 compiler/test/fail_compilation/fail19871.d delete mode 100644 compiler/test/fail_compilation/fail19931.d delete mode 100644 compiler/test/fail_compilation/fail23036.d create mode 100644 compiler/test/runnable/rvalue1.d create mode 100644 compiler/test/runnable/test23036.d diff --git a/.gitignore b/.gitignore index 133aa1cc46c8..5eb15125b43d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ *.[oa] *.deps +*.lst +log* .B* *~ +/compiler/src/dmd/lwv +/compiler/src/dmd/new /compiler/src/bug2 /compiler/test/test_results /compiler/test/trace.def @@ -12,7 +16,6 @@ generated/ -.DS_Store -trace.def -trace.log -*.lst .dub dub.selections.json diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index bdc489871c36..379a22ccc7a7 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -4548,6 +4548,7 @@ struct ASTBase EXP op; ubyte size; ubyte parens; + ubyte rvalue; // consider this an rvalue, even if it is an lvalue Type type; Loc loc; diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index bbfb1ee9f87d..1b838603c7e8 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -1610,13 +1610,15 @@ private Statement generateCopyCtorBody(StructDeclaration sd) * Params: * sd = the `struct` for which the copy constructor is generated * hasCpCtor = set to true if a copy constructor is already present + * hasMoveCtor = set to true if a move constructor is already present * * Returns: * `true` if one needs to be generated * `false` otherwise */ -bool needCopyCtor(StructDeclaration sd, out bool hasCpCtor) +bool needCopyCtor(StructDeclaration sd, out bool hasCpCtor, out bool hasMoveCtor) { + //printf("needCopyCtor() %s\n", sd.toChars()); if (global.errors) return false; @@ -1648,14 +1650,17 @@ bool needCopyCtor(StructDeclaration sd, out bool hasCpCtor) return 0; } - if (isRvalueConstructor(sd, ctorDecl)) + if (ctorDecl.isMoveCtor) rvalueCtor = ctorDecl; return 0; }); + if (rvalueCtor) + hasMoveCtor = true; + if (cpCtor) { - if (rvalueCtor) + if (0 && rvalueCtor) { .error(sd.loc, "`struct %s` may not define both a rvalue constructor and a copy constructor", sd.toChars()); errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); @@ -1710,6 +1715,7 @@ LcheckFields: * Params: * sd = the `struct` for which the copy constructor is generated * sc = the scope where the copy constructor is generated + * hasMoveCtor = set to true when a move constructor is also detected * * Returns: * `true` if `struct` sd defines a copy constructor (explicitly or generated), @@ -1717,10 +1723,10 @@ LcheckFields: * References: * https://dlang.org/spec/struct.html#struct-copy-constructor */ -bool buildCopyCtor(StructDeclaration sd, Scope* sc) +bool buildCopyCtor(StructDeclaration sd, Scope* sc, out bool hasMoveCtor) { bool hasCpCtor; - if (!needCopyCtor(sd, hasCpCtor)) + if (!needCopyCtor(sd, hasCpCtor, hasMoveCtor)) return hasCpCtor; //printf("generating copy constructor for %s\n", sd.toChars()); diff --git a/compiler/src/dmd/declaration.h b/compiler/src/dmd/declaration.h index bdefd2d9f864..a98213d9198c 100644 --- a/compiler/src/dmd/declaration.h +++ b/compiler/src/dmd/declaration.h @@ -782,6 +782,7 @@ class CtorDeclaration final : public FuncDeclaration { public: d_bool isCpCtor; + d_bool isMoveCtor; CtorDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; const char *toChars() const override; diff --git a/compiler/src/dmd/dscope.d b/compiler/src/dmd/dscope.d index aa48d57ba999..76627bec3343 100644 --- a/compiler/src/dmd/dscope.d +++ b/compiler/src/dmd/dscope.d @@ -24,6 +24,7 @@ import dmd.dclass; import dmd.declaration; import dmd.dmodule; import dmd.doc; +import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; @@ -146,6 +147,7 @@ extern (C++) struct Scope AliasDeclaration aliasAsg; /// if set, then aliasAsg is being assigned a new value, /// do not set wasRead for it + StructDeclaration argStruct; /// elimiate recursion when looking for rvalue construction extern (D) __gshared Scope* freelist; diff --git a/compiler/src/dmd/dstruct.d b/compiler/src/dmd/dstruct.d index d7b1ace34f0a..658ecd59787a 100644 --- a/compiler/src/dmd/dstruct.d +++ b/compiler/src/dmd/dstruct.d @@ -101,6 +101,7 @@ extern (C++) class StructDeclaration : AggregateDeclaration bool hasIdentityEquals; // true if has identity opEquals bool hasNoFields; // has no fields bool hasCopyCtor; // copy constructor + bool hasMoveCtor; // move constructor bool hasPointerField; // members with indirections bool hasVoidInitPointers; // void-initialized unsafe fields bool hasUnsafeBitpatterns; // @system members, pointers, bool @@ -320,11 +321,16 @@ extern (C++) class StructDeclaration : AggregateDeclaration import dmd.clone; bool hasCpCtorLocal; - needCopyCtor(this, hasCpCtorLocal); + bool hasMoveCtorLocal; + needCopyCtor(this, hasCpCtorLocal, hasMoveCtorLocal); if (enclosing || // is nested search(this, loc, Id.postblit) || // has postblit search(this, loc, Id.dtor) || // has destructor + /* This is commented out because otherwise buildkite vibe.d: + `canCAS!Task` fails to compile + */ + //hasMoveCtorLocal || // has move constructor hasCpCtorLocal) // has copy constructor { ispod = ThreeState.no; diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 65aab8a6dedc..24482990d8e3 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -256,6 +256,9 @@ Return: */ bool checkHasBothRvalueAndCpCtor(StructDeclaration sd, CtorDeclaration ctor, TemplateInstance ti) { + /* cannot use ctor.isMoveCtor because semantic pass may not have been run yet, + * so use isRvalueConstructor() + */ if (sd && sd.hasCopyCtor && isRvalueConstructor(sd, ctor)) { .error(ctor.loc, "cannot define both an rvalue constructor and a copy constructor for `struct %s`", sd.toChars()); @@ -280,6 +283,7 @@ bool checkHasBothRvalueAndCpCtor(StructDeclaration sd, CtorDeclaration ctor, Tem */ bool isRvalueConstructor(StructDeclaration sd, CtorDeclaration ctor) { + // note commonality with setting isMoveCtor in the semantic code for CtorDeclaration auto tf = ctor.type.isTypeFunction(); const dim = tf.parameterList.length; if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)) @@ -2399,7 +2403,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(CtorDeclaration ctd) { - //printf("CtorDeclaration::semantic() %s\n", toChars()); + //printf("CtorDeclaration::semantic() %p %s\n", ctd, ctd.toChars()); if (ctd.semanticRun >= PASS.semanticdone) return; if (ctd._scope) @@ -2500,12 +2504,15 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor } else if ((dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg))) { - //printf("tf: %s\n", tf.toChars()); + //printf("tf: %s\n", toChars(tf)); auto param = tf.parameterList[0]; - if (param.storageClass & STC.ref_ && param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) + if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) { - //printf("copy constructor\n"); - ctd.isCpCtor = true; + //printf("copy constructor %p\n", ctd); + if (param.storageClass & STC.ref_) + ctd.isCpCtor = true; // copy constructor + else + ctd.isMoveCtor = true; // move constructor } } } @@ -2996,7 +3003,10 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor buildDtors(sd, sc2); - sd.hasCopyCtor = buildCopyCtor(sd, sc2); + bool hasMoveCtor; + sd.hasCopyCtor = buildCopyCtor(sd, sc2, hasMoveCtor); + sd.hasMoveCtor = hasMoveCtor; + sd.postblit = buildPostBlit(sd, sc2); buildOpAssign(sd, sc2); diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index 1d1efd1f966f..5ea7faa3232b 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -5532,6 +5532,8 @@ elem *callfunc(const ref Loc loc, v = ve.var.isVarDeclaration(); bool copy = !(v && (v.isArgDtorVar || v.storage_class & STC.rvalue)); // copy unless the destructor is going to be run on it // then assume the frontend took care of the copying and pass it by ref + if (arg.rvalue) // marked with __rvalue + copy = false; elems[i] = addressElem(ea, arg.type, copy); continue; diff --git a/compiler/src/dmd/expression.d b/compiler/src/dmd/expression.d index dc72b3a8df1b..3d0ae2528460 100644 --- a/compiler/src/dmd/expression.d +++ b/compiler/src/dmd/expression.d @@ -297,6 +297,7 @@ extern (C++) abstract class Expression : ASTNode Loc loc; // file location const EXP op; // to minimize use of dynamic_cast bool parens; // if this is a parenthesized expression + bool rvalue; // true if this is considered to be an rvalue, even if it is an lvalue extern (D) this(const ref Loc loc, EXP op) scope @safe { @@ -1307,7 +1308,7 @@ extern (C++) class IdentifierExp : Expression override final bool isLvalue() { - return true; + return !this.rvalue; } override void accept(Visitor v) @@ -1351,7 +1352,7 @@ extern (C++) final class DsymbolExp : Expression override bool isLvalue() { - return true; + return !rvalue; } override void accept(Visitor v) @@ -1397,7 +1398,7 @@ extern (C++) class ThisExp : Expression override final bool isLvalue() { // Class `this` should be an rvalue; struct `this` should be an lvalue. - return type.toBasetype().ty != Tclass; + return !rvalue && type.toBasetype().ty != Tclass; } override void accept(Visitor v) @@ -1782,7 +1783,7 @@ extern (C++) final class StringExp : Expression /* string literal is rvalue in default, but * conversion to reference of static array is only allowed. */ - return (type && type.toBasetype().ty == Tsarray); + return !rvalue && (type && type.toBasetype().ty == Tsarray); } /******************************** @@ -2719,7 +2720,7 @@ extern (C++) final class VarExp : SymbolExp override bool isLvalue() { - if (var.storage_class & (STC.lazy_ | STC.rvalue | STC.manifest)) + if (rvalue || var.storage_class & (STC.lazy_ | STC.rvalue | STC.manifest)) return false; return true; } @@ -3098,7 +3099,7 @@ extern (C++) class BinAssignExp : BinExp override final bool isLvalue() { - return true; + return !rvalue; } override void accept(Visitor v) @@ -3303,6 +3304,8 @@ extern (C++) final class DotVarExp : UnaExp override bool isLvalue() { + if (rvalue) + return false; if (e1.op != EXP.structLiteral) return true; auto vd = var.isVarDeclaration(); @@ -3530,6 +3533,8 @@ extern (C++) final class CallExp : UnaExp override bool isLvalue() { + if (rvalue) + return false; Type tb = e1.type.toBasetype(); if (tb.ty == Tdelegate || tb.ty == Tpointer) tb = tb.nextOf(); @@ -3648,7 +3653,7 @@ extern (C++) final class PtrExp : UnaExp override bool isLvalue() { - return true; + return !rvalue; } override void accept(Visitor v) @@ -3777,7 +3782,7 @@ extern (C++) final class CastExp : UnaExp override bool isLvalue() { //printf("e1.type = %s, to.type = %s\n", e1.type.toChars(), to.toChars()); - if (!e1.isLvalue()) + if (rvalue || !e1.isLvalue()) return false; return (to.ty == Tsarray && (e1.type.ty == Tvector || e1.type.ty == Tsarray)) || e1.type.mutableOf.unSharedOf().equals(to.mutableOf().unSharedOf()); @@ -3834,7 +3839,7 @@ extern (C++) final class VectorArrayExp : UnaExp override bool isLvalue() { - return e1.isLvalue(); + return !rvalue && e1.isLvalue(); } override void accept(Visitor v) @@ -3891,7 +3896,7 @@ extern (C++) final class SliceExp : UnaExp /* slice expression is rvalue in default, but * conversion to reference of static array is only allowed. */ - return (type && type.toBasetype().ty == Tsarray); + return !rvalue && (type && type.toBasetype().ty == Tsarray); } override Optional!bool toBool() @@ -3956,6 +3961,8 @@ extern (C++) final class ArrayExp : UnaExp override bool isLvalue() { + if (rvalue) + return false; if (type && type.toBasetype().ty == Tvoid) return false; return true; @@ -4005,7 +4012,7 @@ extern (C++) final class CommaExp : BinExp override bool isLvalue() { - return e2.isLvalue(); + return !rvalue && e2.isLvalue(); } override Optional!bool toBool() @@ -4080,7 +4087,7 @@ extern (C++) final class DelegatePtrExp : UnaExp override bool isLvalue() { - return e1.isLvalue(); + return !rvalue && e1.isLvalue(); } override void accept(Visitor v) @@ -4103,7 +4110,7 @@ extern (C++) final class DelegateFuncptrExp : UnaExp override bool isLvalue() { - return e1.isLvalue(); + return !rvalue && e1.isLvalue(); } override void accept(Visitor v) @@ -4143,6 +4150,8 @@ extern (C++) final class IndexExp : BinExp override bool isLvalue() { + if (rvalue) + return false; auto t1b = e1.type.toBasetype(); if (t1b.isTypeAArray() || t1b.isTypeSArray() || (e1.isIndexExp() && t1b != t1b.isTypeDArray())) @@ -4251,7 +4260,7 @@ extern (C++) class AssignExp : BinExp { return false; } - return true; + return !rvalue; } override void accept(Visitor v) @@ -4982,7 +4991,7 @@ extern (C++) final class CondExp : BinExp override bool isLvalue() { - return e1.isLvalue() && e2.isLvalue(); + return !rvalue && e1.isLvalue() && e2.isLvalue(); } override void accept(Visitor v) diff --git a/compiler/src/dmd/expression.h b/compiler/src/dmd/expression.h index c353a191a662..73bb950d32ff 100644 --- a/compiler/src/dmd/expression.h +++ b/compiler/src/dmd/expression.h @@ -78,6 +78,7 @@ class Expression : public ASTNode Loc loc; // file location EXP op; // to minimize use of dynamic_cast d_bool parens; // if this is a parenthesized expression + d_bool rvalue; // consider this an rvalue, even if it is an lvalue size_t size() const; static void _init(); diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 49a8f189a0dc..17a34a70af85 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -846,6 +846,7 @@ extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null) */ private Expression callCpCtor(Scope* sc, Expression e, Type destinationType) { + //printf("callCpCtor(e: %s et: %s destinationType: %s\n", toChars(e), toChars(e.type), toChars(destinationType)); auto ts = e.type.baseElemOf().isTypeStruct(); if (!ts) @@ -2952,7 +2953,7 @@ private bool functionParameters(const ref Loc loc, Scope* sc, Type* prettype, Expression* peprefix) { Expressions* arguments = argumentList.arguments; - //printf("functionParameters() %s\n", fd ? fd.toChars() : ""); + //printf("functionParameters() fd: %s tf: %s\n", fd ? fd.ident.toChars() : "", toChars(tf)); assert(arguments); assert(fd || tf.next); const size_t nparams = tf.parameterList.length; @@ -3892,6 +3893,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } + scope (success) result.rvalue = exp.rvalue; + Dsymbol scopesym; Dsymbol s = sc.search(exp.loc, exp.ident, scopesym); if (s) @@ -6718,7 +6721,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor errorSupplemental(exp.loc, "%s", failMessage); } - if (tf.callMatch(null, exp.argumentList, 0, &errorHelper, sc) == MATCH.nomatch) + if (callMatch(exp.f, tf, null, exp.argumentList, 0, &errorHelper, sc) == MATCH.nomatch) return setError(); // Purity and safety check should run after testing arguments matching @@ -6801,7 +6804,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor exp.f = null; } - if (tf.callMatch(null, exp.argumentList, 0, &errorHelper2, sc) == MATCH.nomatch) + if (callMatch(exp.f, tf, null, exp.argumentList, 0, &errorHelper2, sc) == MATCH.nomatch) exp.f = null; } if (!exp.f || exp.f.errors) @@ -10409,9 +10412,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor { static if (LOGSEMANTIC) { - if (exp.op == EXP.blit) printf("BlitExp.toElem('%s')\n", exp.toChars()); - if (exp.op == EXP.assign) printf("AssignExp.toElem('%s')\n", exp.toChars()); - if (exp.op == EXP.construct) printf("ConstructExp.toElem('%s')\n", exp.toChars()); + if (exp.op == EXP.blit) printf("BlitExp.semantic('%s')\n", exp.toChars()); + if (exp.op == EXP.assign) printf("AssignExp.semantic('%s')\n", exp.toChars()); + if (exp.op == EXP.construct) printf("ConstructExp.semantic('%s')\n", exp.toChars()); } void setResult(Expression e, int line = __LINE__) @@ -10927,6 +10930,24 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } } + else if (sd.hasMoveCtor) + { + /* Rewrite as: + * e1 = init, e1.moveCtor(e2); + */ + Expression einit = new BlitExp(exp.loc, exp.e1, getInitExp(sd, exp.loc, sc, t1)); + einit.type = e1x.type; + + Expression e; + e = new DotIdExp(exp.loc, e1x, Id.ctor); + e = new CallExp(exp.loc, e, e2x); + e = new CommaExp(exp.loc, einit, e); + + //printf("e: %s\n", e.toChars()); + + result = e.expressionSemantic(sc); + return; + } else { /* The struct value returned from the function is transferred diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index fcd1888a19ed..32661e3a239c 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -2068,6 +2068,7 @@ enum class EXP : uint8_t _Generic_ = 125u, interval = 126u, loweredAssignExp = 127u, + rvalue = 128u, }; typedef uint64_t dinteger_t; @@ -2105,6 +2106,7 @@ class Expression : public ASTNode Loc loc; const EXP op; bool parens; + bool rvalue; size_t size() const; static void _init(); static void deinitialize(); @@ -2888,33 +2890,34 @@ enum class TOK : uint8_t wchar_tLiteral = 196u, endOfLine = 197u, whitespace = 198u, - inline_ = 199u, - register_ = 200u, - restrict_ = 201u, - signed_ = 202u, - sizeof_ = 203u, - typedef_ = 204u, - unsigned_ = 205u, - volatile_ = 206u, - _Alignas_ = 207u, - _Alignof_ = 208u, - _Atomic_ = 209u, - _Bool_ = 210u, - _Complex_ = 211u, - _Generic_ = 212u, - _Imaginary_ = 213u, - _Noreturn_ = 214u, - _Static_assert_ = 215u, - _Thread_local_ = 216u, - _assert_ = 217u, - _import_ = 218u, - __cdecl_ = 219u, - __declspec_ = 220u, - __stdcall_ = 221u, - __thread_ = 222u, - __pragma_ = 223u, - __int128_ = 224u, - __attribute___ = 225u, + rvalue = 199u, + inline_ = 200u, + register_ = 201u, + restrict_ = 202u, + signed_ = 203u, + sizeof_ = 204u, + typedef_ = 205u, + unsigned_ = 206u, + volatile_ = 207u, + _Alignas_ = 208u, + _Alignof_ = 209u, + _Atomic_ = 210u, + _Bool_ = 211u, + _Complex_ = 212u, + _Generic_ = 213u, + _Imaginary_ = 214u, + _Noreturn_ = 215u, + _Static_assert_ = 216u, + _Thread_local_ = 217u, + _assert_ = 218u, + _import_ = 219u, + __cdecl_ = 220u, + __declspec_ = 221u, + __stdcall_ = 222u, + __thread_ = 223u, + __pragma_ = 224u, + __int128_ = 225u, + __attribute___ = 226u, }; class FuncExp final : public Expression @@ -3792,6 +3795,7 @@ class CtorDeclaration final : public FuncDeclaration { public: bool isCpCtor; + bool isMoveCtor; CtorDeclaration* syntaxCopy(Dsymbol* s) override; const char* kind() const override; const char* toChars() const override; @@ -5260,18 +5264,18 @@ struct UnionExp final private: union _AnonStruct_u { - char exp[30LLU]; + char exp[31LLU]; char integerexp[40LLU]; - char errorexp[30LLU]; + char errorexp[31LLU]; char realexp[48LLU]; char complexexp[64LLU]; char symoffexp[64LLU]; - char stringexp[51LLU]; - char arrayliteralexp[48LLU]; + char stringexp[59LLU]; + char arrayliteralexp[56LLU]; char assocarrayliteralexp[56LLU]; char structliteralexp[76LLU]; char compoundliteralexp[40LLU]; - char nullexp[30LLU]; + char nullexp[31LLU]; char dotvarexp[49LLU]; char addrexp[40LLU]; char indexexp[74LLU]; @@ -7130,6 +7134,7 @@ struct Scope final void* anchorCounts; Identifier* prevAnchor; AliasDeclaration* aliasAsg; + StructDeclaration* argStruct; Dsymbol* search(const Loc& loc, Identifier* ident, Dsymbol*& pscopesym, uint32_t flags = 0u); Scope() : enclosing(), @@ -7170,10 +7175,11 @@ struct Scope final userAttribDecl(), lastdc(), prevAnchor(), - aliasAsg() + aliasAsg(), + argStruct() { } - Scope(Scope* enclosing, Module* _module = nullptr, ScopeDsymbol* scopesym = nullptr, FuncDeclaration* func = nullptr, VarDeclaration* varDecl = nullptr, Dsymbol* parent = nullptr, LabelStatement* slabel = nullptr, SwitchStatement* sw = nullptr, Statement* tryBody = nullptr, TryFinallyStatement* tf = nullptr, ScopeGuardStatement* os = nullptr, Statement* sbreak = nullptr, Statement* scontinue = nullptr, ForeachStatement* fes = nullptr, Scope* callsc = nullptr, Dsymbol* inunion = nullptr, bool nofree = false, bool inLoop = false, bool inDefaultArg = false, int32_t intypeof = 0, VarDeclaration* lastVar = nullptr, ErrorSink* eSink = nullptr, Module* minst = nullptr, TemplateInstance* tinst = nullptr, CtorFlow ctorflow = CtorFlow(), AlignDeclaration* aligndecl = nullptr, CPPNamespaceDeclaration* namespace_ = nullptr, LINK linkage = (LINK)1u, CPPMANGLE cppmangle = (CPPMANGLE)0u, PragmaDeclaration* inlining = nullptr, Visibility visibility = Visibility((Visibility::Kind)5u, nullptr), int32_t explicitVisibility = 0, uint64_t stc = 0LLU, DeprecatedDeclaration* depdecl = nullptr, uint32_t bitFields = 0u, UserAttributeDeclaration* userAttribDecl = nullptr, DocComment* lastdc = nullptr, void* anchorCounts = nullptr, Identifier* prevAnchor = nullptr, AliasDeclaration* aliasAsg = nullptr) : + Scope(Scope* enclosing, Module* _module = nullptr, ScopeDsymbol* scopesym = nullptr, FuncDeclaration* func = nullptr, VarDeclaration* varDecl = nullptr, Dsymbol* parent = nullptr, LabelStatement* slabel = nullptr, SwitchStatement* sw = nullptr, Statement* tryBody = nullptr, TryFinallyStatement* tf = nullptr, ScopeGuardStatement* os = nullptr, Statement* sbreak = nullptr, Statement* scontinue = nullptr, ForeachStatement* fes = nullptr, Scope* callsc = nullptr, Dsymbol* inunion = nullptr, bool nofree = false, bool inLoop = false, bool inDefaultArg = false, int32_t intypeof = 0, VarDeclaration* lastVar = nullptr, ErrorSink* eSink = nullptr, Module* minst = nullptr, TemplateInstance* tinst = nullptr, CtorFlow ctorflow = CtorFlow(), AlignDeclaration* aligndecl = nullptr, CPPNamespaceDeclaration* namespace_ = nullptr, LINK linkage = (LINK)1u, CPPMANGLE cppmangle = (CPPMANGLE)0u, PragmaDeclaration* inlining = nullptr, Visibility visibility = Visibility((Visibility::Kind)5u, nullptr), int32_t explicitVisibility = 0, uint64_t stc = 0LLU, DeprecatedDeclaration* depdecl = nullptr, uint32_t bitFields = 0u, UserAttributeDeclaration* userAttribDecl = nullptr, DocComment* lastdc = nullptr, void* anchorCounts = nullptr, Identifier* prevAnchor = nullptr, AliasDeclaration* aliasAsg = nullptr, StructDeclaration* argStruct = nullptr) : enclosing(enclosing), _module(_module), scopesym(scopesym), @@ -7213,7 +7219,8 @@ struct Scope final lastdc(lastdc), anchorCounts(anchorCounts), prevAnchor(prevAnchor), - aliasAsg(aliasAsg) + aliasAsg(aliasAsg), + argStruct(argStruct) {} }; @@ -7242,6 +7249,8 @@ class StructDeclaration : public AggregateDeclaration bool hasNoFields(bool v); bool hasCopyCtor() const; bool hasCopyCtor(bool v); + bool hasMoveCtor() const; + bool hasMoveCtor(bool v); bool hasPointerField() const; bool hasPointerField(bool v); bool hasVoidInitPointers() const; diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 2db1b12893ff..9c5a3d0bbb96 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -1370,11 +1370,13 @@ extern (C++) final class FuncLiteralDeclaration : FuncDeclaration */ extern (C++) final class CtorDeclaration : FuncDeclaration { - bool isCpCtor; - extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc, Type type, bool isCpCtor = false) + bool isCpCtor; // copy constructor + bool isMoveCtor; // move constructor (aka rvalue constructor) + extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc, Type type, bool isCpCtor = false, bool isMoveCtor = false) { super(loc, endloc, Id.ctor, stc, type); this.isCpCtor = isCpCtor; + this.isMoveCtor = isMoveCtor; //printf("CtorDeclaration(loc = %s) %s %p\n", loc.toChars(), toChars(), this); } diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index 689aa57dab3a..c613b57d08ba 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -2088,7 +2088,7 @@ MATCH leastAsSpecialized(FuncDeclaration f, FuncDeclaration g, Identifiers* name args.push(e); } - MATCH m = tg.callMatch(null, ArgumentList(&args, names), 1); + MATCH m = callMatch(g, tg, null, ArgumentList(&args, names), 1); if (m > MATCH.nomatch) { /* A variadic parameter list is less specialized than a diff --git a/compiler/src/dmd/globals.h b/compiler/src/dmd/globals.h index c5659ea10b62..d162f278561e 100644 --- a/compiler/src/dmd/globals.h +++ b/compiler/src/dmd/globals.h @@ -192,7 +192,7 @@ struct Param // https://digitalmars.com/d/archives/digitalmars/D/Binding_rvalues_to_ref_parameters_redux_325087.html // Implementation: https://github.com/dlang/dmd/pull/9817 FeatureState safer; // safer by default (more @safe checks in unattributed code) - // https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md + // https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md FeatureState noSharedAccess; // read/write access to shared memory objects d_bool previewIn; // `in` means `[ref] scope const`, accepts rvalues diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 4da5b162850a..ff9eb41b3f83 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -2869,6 +2869,12 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt buf.writestring(e.value.toChars()); } + if (e.rvalue) + buf.writestring("__rvalue("); + scope (success) + if (e.rvalue) + buf.writeByte(')'); + switch (e.op) { default: diff --git a/compiler/src/dmd/mtype.d b/compiler/src/dmd/mtype.d index 6d57467314ce..9ebdbd424a39 100644 --- a/compiler/src/dmd/mtype.d +++ b/compiler/src/dmd/mtype.d @@ -1609,7 +1609,7 @@ extern (C++) abstract class TypeNext : Type /******************************* * For TypeFunction, nextOf() can return NULL if the function return - * type is meant to be inferred, and semantic() hasn't yet ben run + * type is meant to be inferred, and semantic() hasn't yet been run * on the function. After semantic(), it must no longer be NULL. */ override final Type nextOf() @safe diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 43fcaf933545..e85d35cf8106 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -5880,6 +5880,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: + case TOK.rvalue: Lexp: { AST.Expression exp = parseExpression(); @@ -8426,6 +8427,15 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer e = new AST.TypeidExp(loc, o); break; } + case TOK.rvalue: + { + nextToken(); + check(TOK.leftParenthesis, "`__rvalue`"); + e = parseAssignExp(); + e.rvalue = true; + check(TOK.rightParenthesis); + break; + } case TOK.traits: { /* __traits(identifier, args...) diff --git a/compiler/src/dmd/scope.h b/compiler/src/dmd/scope.h index 7983a7ac93f2..ac112664ae8a 100644 --- a/compiler/src/dmd/scope.h +++ b/compiler/src/dmd/scope.h @@ -143,6 +143,7 @@ struct Scope final AliasDeclaration *aliasAsg; // if set, then aliasAsg is being assigned a new value, // do not set wasRead for it + StructDeclaration *argStruct; // elimiate recursion when looking for rvalue construction Dsymbol *search(const Loc &loc, Identifier *ident, Dsymbol *&pscopesym, SearchOptFlags flags = (SearchOptFlags)SearchOpt::all); }; diff --git a/compiler/src/dmd/templatesem.d b/compiler/src/dmd/templatesem.d index 0e9c4338a052..82174b53a72c 100644 --- a/compiler/src/dmd/templatesem.d +++ b/compiler/src/dmd/templatesem.d @@ -1993,7 +1993,7 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, tf.mod = tthis_fd.mod; } const(char)* failMessage; - MATCH mfa = tf.callMatch(tthis_fd, argumentList, 0, errorHelper, sc); + MATCH mfa = callMatch(fd, tf, tthis_fd, argumentList, 0, errorHelper, sc); //printf("test1: mfa = %d\n", mfa); if (failMessage) errorHelper(failMessage); @@ -2198,7 +2198,7 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, Type tthis_fd = fd.needThis() && !fd.isCtorDeclaration() ? tthis : null; auto tf = fd.type.isTypeFunction(); - MATCH mfa = tf.callMatch(tthis_fd, argumentList, 0, null, sc); + MATCH mfa = callMatch(fd, tf, tthis_fd, argumentList, 0, null, sc); if (mfa < m.last) return 0; @@ -2300,8 +2300,8 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, // Disambiguate by tf.callMatch auto tf1 = fd.type.isTypeFunction(); auto tf2 = m.lastf.type.isTypeFunction(); - MATCH c1 = tf1.callMatch(tthis_fd, argumentList, 0, null, sc); - MATCH c2 = tf2.callMatch(tthis_best, argumentList, 0, null, sc); + MATCH c1 = callMatch(fd, tf1, tthis_fd, argumentList, 0, null, sc); + MATCH c2 = callMatch(m.lastf, tf2, tthis_best, argumentList, 0, null, sc); //printf("2: c1 = %d, c2 = %d\n", c1, c2); if (c1 > c2) goto Ltd; if (c1 < c2) goto Ltd_best; @@ -2404,7 +2404,7 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, if (m.lastf.type.ty == Terror) goto Lerror; auto tf = m.lastf.type.isTypeFunction(); - if (!tf.callMatch(tthis_best, argumentList, 0, null, sc)) + if (callMatch(m.lastf, tf, tthis_best, argumentList, 0, null, sc) == MATCH.nomatch) goto Lnomatch; /* As https://issues.dlang.org/show_bug.cgi?id=3682 shows, diff --git a/compiler/src/dmd/tokens.d b/compiler/src/dmd/tokens.d index da4a3ee209ef..b499c008eab3 100644 --- a/compiler/src/dmd/tokens.d +++ b/compiler/src/dmd/tokens.d @@ -27,6 +27,9 @@ enum TOK : ubyte { reserved, + // if this list changes, update + // tokens.h, ../tests/cxxfrontend.cc and ../../test/unit/lexer/location_offset.d to match + // Other leftParenthesis, rightParenthesis, @@ -249,6 +252,7 @@ enum TOK : ubyte wchar_tLiteral, endOfLine, // \n, \r, \u2028, \u2029 whitespace, + rvalue, // C only keywords inline, @@ -425,6 +429,7 @@ enum EXP : ubyte interval, loweredAssignExp, + rvalue, } enum FirstCKeyword = TOK.inline; @@ -556,6 +561,7 @@ private immutable TOK[] keywords = TOK.prettyFunction, TOK.shared_, TOK.immutable_, + TOK.rvalue, // C only keywords TOK.inline, @@ -680,6 +686,7 @@ extern (C++) struct Token TOK.pragma_: "pragma", TOK.typeof_: "typeof", TOK.typeid_: "typeid", + TOK.rvalue: "__rvalue", TOK.template_: "template", TOK.void_: "void", TOK.int8: "byte", diff --git a/compiler/src/dmd/tokens.h b/compiler/src/dmd/tokens.h index 929897a3fa6f..2a984b4b7b62 100644 --- a/compiler/src/dmd/tokens.h +++ b/compiler/src/dmd/tokens.h @@ -258,6 +258,7 @@ enum class TOK : unsigned char wchar_tLiteral, endOfLine, // \n, \r, \u2028, \u2029 whitespace, + rvalue, // C only keywords inline_, diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index aea969a2e08d..6d2d9212e9ec 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -681,6 +681,7 @@ extern (D) bool checkComplexTransition(Type type, const ref Loc loc, Scope* sc) * 'args' are being matched to function type 'tf' * Determine match level. * Params: + * fd = function being called, if a symbol * tf = function type * tthis = type of `this` pointer, null if not member function * argumentList = arguments to function call @@ -690,9 +691,10 @@ extern (D) bool checkComplexTransition(Type type, const ref Loc loc, Scope* sc) * Returns: * MATCHxxxx */ -extern (D) MATCH callMatch(TypeFunction tf, Type tthis, ArgumentList argumentList, int flag = 0, void delegate(const(char)*) scope errorHelper = null, Scope* sc = null) +extern (D) MATCH callMatch(FuncDeclaration fd, TypeFunction tf, Type tthis, ArgumentList argumentList, + int flag = 0, void delegate(const(char)*) scope errorHelper = null, Scope* sc = null) { - //printf("TypeFunction::callMatch() %s\n", tf.toChars()); + //printf("callMatch() fd: %s, tf: %s\n", fd ? fd.ident.toChars() : "null", toChars(tf)); MATCH match = MATCH.exact; // assume exact match ubyte wildmatch = 0; @@ -820,7 +822,7 @@ extern (D) MATCH callMatch(TypeFunction tf, Type tthis, ArgumentList argumentLis Expression arg = args[u]; if (!arg) continue; // default argument - m = argumentMatchParameter(tf, p, arg, wildmatch, flag, sc, pMessage); + m = argumentMatchParameter(fd, tf, p, arg, wildmatch, flag, sc, pMessage); if (failMessage) { buf.reset(); @@ -887,14 +889,15 @@ extern (D) MATCH callMatch(TypeFunction tf, Type tthis, ArgumentList argumentLis * * This is done by seeing if a call to the copy constructor can be made: * ``` - * typeof(tprm) __copytmp; - * copytmp.__copyCtor(arg); + * typeof(tprm) __copytemp; + * copytemp.__copyCtor(arg); * ``` */ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, Expression arg, Type tprm, Scope* sc, const(char)** pMessage) { - auto tmp = new VarDeclaration(arg.loc, tprm, Identifier.generateId("__copytmp"), null); + //printf("isCopyConstructorCallable() argStruct: %s arg: %s tprm: %s\n", argStruct.toChars(), toChars(arg), toChars(tprm)); + auto tmp = new VarDeclaration(arg.loc, tprm, Identifier.generateId("__copytemp"), null); tmp.storage_class = STC.rvalue | STC.temp | STC.ctfe; tmp.dsymbolSemantic(sc); Expression ve = new VarExp(arg.loc, tmp); @@ -980,25 +983,28 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, * * This function is called by `TypeFunction.callMatch` while iterating over * the list of parameter. Here we check if `arg` is a match for `p`, - * which is mostly about checking if `arg.type` converts to `p`'s type + * which is mostly about checking if `arg.type` converts to type of `p` * and some check about value reference. * * Params: + * fd = the function being called if symbol, null if not * tf = The `TypeFunction`, only used for error reporting * p = The parameter of `tf` being matched * arg = Argument being passed (bound) to `p` * wildmatch = Wild (`inout`) matching level, derived from the full argument list - * flag = A non-zero value means we're doing a partial ordering check + * flag = A non-zero value means we are doing a partial ordering check * (no value semantic check) * sc = Scope we are in * pMessage = A buffer to write the error in, or `null` * * Returns: Whether `trailingArgs` match `p`. */ -private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, +private extern(D) MATCH argumentMatchParameter (FuncDeclaration fd, TypeFunction tf, Parameter p, Expression arg, ubyte wildmatch, int flag, Scope* sc, const(char)** pMessage) { - //printf("arg: %s, type: %s\n", arg.toChars(), arg.type.toChars()); + static if (0) + printf("argumentMatchParameter() sc: %p, fd: %s, tf: %s, p: %s, arg: %s, arg.type: %s\n", + sc, fd ? fd.ident.toChars() : "null", tf.toChars(), parameterToChars(p, tf, false), arg.toChars(), arg.type.toChars()); MATCH m; Type targ = arg.type; Type tprm = wildmatch ? p.type.substWildTo(wildmatch) : p.type; @@ -1013,18 +1019,47 @@ private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, else { const isRef = p.isReference(); - StructDeclaration argStruct, prmStruct; - // first look for a copy constructor - if (arg.isLvalue() && !isRef && targ.ty == Tstruct && tprm.ty == Tstruct) + StructDeclaration argStruct, prmStruct; + if (targ.ty == Tstruct && tprm.ty == Tstruct) { // if the argument and the parameter are of the same unqualified struct type argStruct = (cast(TypeStruct)targ).sym; prmStruct = (cast(TypeStruct)tprm).sym; + + /* if both a copy constructor and move constructor exist, then match + * the lvalue to the copy constructor only and the rvalue to the move constructor + * only + */ + if (argStruct == prmStruct && fd) + { + if (auto cfd = fd.isCtorDeclaration()) + { + /* Get struct that constructor is making + */ + + auto t1 = cfd.type.toBasetype(); + auto t2 = t1.nextOf(); + auto t3 = t2.isTypeStruct(); + if (t3) + { + auto ctorStruct = t3.sym; +// StructDeclaration ctorStruct = cfd.type.toBasetype().nextOf().isTypeStruct().sym; + + if (prmStruct == ctorStruct && ctorStruct.hasCopyCtor && ctorStruct.hasMoveCtor) + { + if (cfd.isCpCtor && !arg.isLvalue()) + return MATCH.nomatch; // copy constructor is only for lvalues + else if (cfd.isMoveCtor && arg.isLvalue()) + return MATCH.nomatch; // move constructor is only for rvalues + } + } + } + } } // check if the copy constructor may be called to copy the argument - if (argStruct && argStruct == prmStruct && argStruct.hasCopyCtor) + if (arg.isLvalue() && !isRef && argStruct && argStruct == prmStruct && argStruct.hasCopyCtor) { if (!isCopyConstructorCallable(argStruct, arg, tprm, sc, pMessage)) return MATCH.nomatch; diff --git a/compiler/test/fail_compilation/fail19871.d b/compiler/test/fail_compilation/fail19871.d deleted file mode 100644 index ad458df20019..000000000000 --- a/compiler/test/fail_compilation/fail19871.d +++ /dev/null @@ -1,20 +0,0 @@ -/* -TEST_OUTPUT: ---- -fail_compilation/fail19871.d(10): Error: `struct Struct` may not define both a rvalue constructor and a copy constructor -fail_compilation/fail19871.d(19): rvalue constructor defined here -fail_compilation/fail19871.d(13): copy constructor defined here ---- -*/ - -struct Struct -{ - @disable this(); - this(ref Struct other) - { - const Struct s = void; - this(s); - } - - this(Struct) {} -} diff --git a/compiler/test/fail_compilation/fail19931.d b/compiler/test/fail_compilation/fail19931.d deleted file mode 100644 index 940a1faee028..000000000000 --- a/compiler/test/fail_compilation/fail19931.d +++ /dev/null @@ -1,15 +0,0 @@ -/* -TEST_OUTPUT: ---- -fail_compilation/fail19931.d(10): Error: `struct S` may not define both a rvalue constructor and a copy constructor -fail_compilation/fail19931.d(12): rvalue constructor defined here -fail_compilation/fail19931.d(13): copy constructor defined here ---- -*/ - -struct S -{ - this(S s) {} - this(ref S s) {} - this(this) {} -} diff --git a/compiler/test/fail_compilation/fail23036.d b/compiler/test/fail_compilation/fail23036.d deleted file mode 100644 index 8920586c67a8..000000000000 --- a/compiler/test/fail_compilation/fail23036.d +++ /dev/null @@ -1,22 +0,0 @@ -// https://issues.dlang.org/show_bug.cgi?id=23036 - -/* -TEST_OUTPUT: ---- -fail_compilation/fail23036.d(12): Error: `struct S` may not define both a rvalue constructor and a copy constructor -fail_compilation/fail23036.d(15): rvalue constructor defined here -fail_compilation/fail23036.d(14): copy constructor defined here ---- -*/ - -struct S -{ - this(ref S) {} - this(S, int a = 2) {} -} - -void main() -{ - S a; - S b = a; -} diff --git a/compiler/test/runnable/rvalue1.d b/compiler/test/runnable/rvalue1.d new file mode 100644 index 000000000000..b1311a108da9 --- /dev/null +++ b/compiler/test/runnable/rvalue1.d @@ -0,0 +1,96 @@ +/* PERMUTE_ARGS: -preview=rvaluerefparam +/* testing __rvalue */ + +import core.stdc.stdio; + +/********************************/ + +int foo(int) { printf("foo(int)\n"); return 1; } +int foo(ref int) { printf("foo(ref int)\n"); return 2; } + +void test1() +{ + int s; + assert(foo(s) == 2); + assert(foo(__rvalue(s)) == 1); +} + +/********************************/ + +struct S +{ + nothrow: + ~this() { printf("~this() %p\n", &this); } + this(ref S) { printf("this(ref S)\n"); } + void opAssign(S) { printf("opAssign(S)\n"); } +} + +void test2() +{ + S s; + S t; + + t = __rvalue(s); +} + +/********************************/ + +struct S3 +{ + int a, b, c; + + this(S3) {} + this(ref S3) {} +} + +void test3() +{ + S3 s; + S3 x = s; // this line causes the compiler to crash +} + +/********************************/ + +struct S4 +{ + void* p; + + this(S4 s) + { + assert(&s is &x); // confirm the rvalue reference + } +} + +__gshared S4 x; + +void test4() +{ + S4 t = __rvalue(x); +} + +/********************************/ + +struct S5 +{ + this(S5 s) { printf("this(S5 s)\n"); } + this(ref inout S5 s) inout { printf("this(ref inout S5 s) inout\n"); } +} + +void test5() +{ + S5 t; + S5 t1 = t; + S5 t2 = __rvalue(t); +} + +/********************************/ + +int main() +{ + test1(); + test2(); + test3(); + test4(); + test5(); + return 0; +} diff --git a/compiler/test/runnable/test23036.d b/compiler/test/runnable/test23036.d new file mode 100644 index 000000000000..efb6fef3953b --- /dev/null +++ b/compiler/test/runnable/test23036.d @@ -0,0 +1,13 @@ +// https://issues.dlang.org/show_bug.cgi?id=23036 + +struct S +{ + this(ref S) {} + this(S, int a = 2) {} +} + +void main() +{ + S a; + S b = a; +} diff --git a/compiler/test/unit/lexer/location_offset.d b/compiler/test/unit/lexer/location_offset.d index 21266276d2c3..fb7edfd5eb21 100644 --- a/compiler/test/unit/lexer/location_offset.d +++ b/compiler/test/unit/lexer/location_offset.d @@ -399,6 +399,7 @@ enum Test[string] tests = [ "auto_" : Test("auto"), "package_" : Test("package"), "immutable_" : Test("immutable"), + "rvalue" : Test("__rvalue"), "if_" : Test("if"), "else_" : Test("else"),