add __rvalue(expression) builtin (#17050)

This commit is contained in:
Walter Bright 2024-12-15 12:13:28 -08:00 committed by GitHub
parent 13775eb2d1
commit a99a3894be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 471 additions and 163 deletions

5
.gitignore vendored
View file

@ -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

23
changelog/dmd.rvalue.dd Normal file
View file

@ -0,0 +1,23 @@
Add primary expression of the form `__rvalue(expression)` which causes `expression` to be treated as an rvalue, even if it is an lvalue.
Overloads on `ref`:
```
foo(S s); // selected if `s` is an rvalue
foo(ref S s); // selected if argument `s` is an lvalue
S s;
S bar();
...
foo(s); // selects foo(ref S)
foo(bar()); // selects foo(S)
```
With this change,
```
foo(__rvalue(s)); // selects foo(S)
```
This also applies to constructors and assignments, meaning move constructors and
move assignments are enabled. Moving instead of copying can be much more resource
efficient, as, say, a string can be moved rather than copied/deleted.
A moved object can still be destructed, so take that into account when moving
a field - set it to a benign value that can be destructed.

View file

@ -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;

View file

@ -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());

View file

@ -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;

View file

@ -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;

View file

@ -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
@ -321,11 +322,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;

View file

@ -256,6 +256,10 @@ Return:
*/
bool checkHasBothRvalueAndCpCtor(StructDeclaration sd, CtorDeclaration ctor, TemplateInstance ti)
{
//printf("checkHasBothRvalueAndCpCtor() sd: %s ctor: %s ti: %s\n", sd.toChars(), ctor.toChars(), ti.toChars());
/* 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 +284,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))
@ -306,6 +311,7 @@ bool isRvalueConstructor(StructDeclaration sd, CtorDeclaration ctor)
*/
Expression resolveAliasThis(Scope* sc, Expression e, bool gag = false, bool findOnly = false)
{
//printf("resolveAliasThis() %s\n", toChars(e));
import dmd.typesem : dotExp;
for (AggregateDeclaration ad = isAggregate(e.type); ad;)
{
@ -2399,7 +2405,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 +2506,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 +3005,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);

View file

@ -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;

View file

@ -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)

View file

@ -78,6 +78,7 @@ public:
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();

View file

@ -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__)
@ -10881,6 +10884,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
/* We have a copy constructor for this
*/
//printf("exp: %s\n", toChars(exp));
//printf("e2x: %s\n", toChars(e2x));
if (e2x.isLvalue())
{
if (sd.hasCopyCtor)
@ -10927,6 +10932,37 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return;
}
}
else if (sd.hasMoveCtor && !e2x.isCallExp() && !e2x.isStructLiteralExp())
{
/* The !e2x.isCallExp() is because it is already an rvalue
and the move constructor is unnecessary:
struct S {
alias TT this;
long TT();
this(T)(int x) {}
this(S);
this(ref S);
~this();
}
S fun(ref S arg);
void test() { S st; fun(st); }
*/
/* 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
@ -15487,6 +15523,7 @@ Expression resolveLoc(Expression exp, const ref Loc loc, Scope* sc)
*/
Expression addDtorHook(Expression e, Scope* sc)
{
//printf("addDtorHook() %s\n", toChars(e));
Expression visit(Expression exp)
{
return exp;

View file

@ -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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
lastdc(lastdc),
anchorCounts(anchorCounts),
prevAnchor(prevAnchor),
aliasAsg(aliasAsg)
aliasAsg(aliasAsg),
argStruct(argStruct)
{}
};
@ -7242,6 +7249,8 @@ public:
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;

View file

@ -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);
}

View file

@ -1529,6 +1529,7 @@ enum FuncResolveFlag : ubyte
FuncDeclaration resolveFuncCall(const ref Loc loc, Scope* sc, Dsymbol s,
Objects* tiargs, Type tthis, ArgumentList argumentList, FuncResolveFlag flags)
{
//printf("resolveFuncCall() %s\n", s.toChars());
auto fargs = argumentList.arguments;
if (!s)
return null; // no match
@ -2088,7 +2089,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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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...)

View file

@ -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);
};

View file

@ -623,7 +623,7 @@ MATCH leastAsSpecialized(Scope* sc, TemplateDeclaration td, TemplateDeclaration
enum LOG_LEASTAS = 0;
static if (LOG_LEASTAS)
{
printf("%s.leastAsSpecialized(%s)\n", toChars(), td2.toChars());
printf("%s.leastAsSpecialized(%s)\n", td.toChars(), td2.toChars());
}
/* This works by taking the template parameters to this template
@ -1890,23 +1890,22 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc,
{
version (none)
{
printf("functionResolve() dstart = %s\n", dstart.toChars());
printf(" tiargs:\n");
if (tiargs)
printf("functionResolve() dstart: %s (", dstart.toChars());
for (size_t i = 0; i < (tiargs ? (*tiargs).length : 0); i++)
{
for (size_t i = 0; i < tiargs.length; i++)
{
RootObject arg = (*tiargs)[i];
printf("\t%s\n", arg.toChars());
}
if (i) printf(", ");
RootObject arg = (*tiargs)[i];
printf("%s", arg.toChars());
}
printf(" fargs:\n");
for (size_t i = 0; i < (fargs ? fargs.length : 0); i++)
printf(")(");
for (size_t i = 0; i < (argumentList.arguments ? (*argumentList.arguments).length : 0); i++)
{
Expression arg = (*fargs)[i];
printf("\t%s %s\n", arg.type.toChars(), arg.toChars());
if (i) printf(", ");
Expression arg = (*argumentList.arguments)[i];
printf("%s %s", arg.type.toChars(), arg.toChars());
//printf("\tty = %d\n", arg.type.ty);
}
printf(")\n");
//printf("stc = %llx\n", dstart._scope.stc);
//printf("match:t/f = %d/%d\n", ta_last, m.last);
}
@ -1993,7 +1992,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 +2197,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 +2299,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 +2403,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,

View file

@ -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",

View file

@ -258,6 +258,7 @@ enum class TOK : unsigned char
wchar_tLiteral,
endOfLine, // \n, \r, \u2028, \u2029
whitespace,
rvalue,
// C only keywords
inline_,

View file

@ -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;

View file

@ -0,0 +1,22 @@
/* PERMUTE_ARGS: -preview=rvaluerefparam
*/
struct S
{
alias TT this;
long TT();
this(T)(int x) {} // works if `int` is `long`
this(S);
this(ref S);
~this();
}
S fun(ref S arg);
void test()
{
S st;
fun(st);
}

View file

@ -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) {}
}

View file

@ -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) {}
}

View file

@ -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;
}

View file

@ -0,0 +1,155 @@
/* 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 moveCtor, copyCtor, moveAss, copyAss;
struct S6
{
this(S6 s) { ++moveCtor; }
this(ref S6 s) { ++copyCtor; }
void opAssign(S6 s) { ++moveAss; }
void opAssign(ref S6 s) { ++copyAss; }
}
void test6()
{
S6 x;
S6 s = x;
// printf("S6 s = x; moveCtor %d copyCtor %d moveAss %d copyAss %d\n", moveCtor, copyCtor, moveAss, copyAss);
assert(copyCtor == 1);
S6 s2 = __rvalue(x);
// printf("S6 s2 = __rvalue(x); moveCtor %d copyCtor %d moveAss %d copyAss %d\n", moveCtor, copyCtor, moveAss, copyAss);
assert(moveCtor == 1);
s2 = s;
// printf("s2 = s; moveCtor %d copyCtor %d moveAss %d copyAss %d\n", moveCtor, copyCtor, moveAss, copyAss);
assert(copyAss == 1);
s2 = __rvalue(s);
// printf("s2 = __rvalue(s); moveCtor %d copyCtor %d moveAss %d copyAss %d\n", moveCtor, copyCtor, moveAss, copyAss);
assert(moveAss == 1);
assert(copyCtor == 1 && moveCtor == 1 && copyAss == 1 && moveAss == 1);
}
/********************************/
// https://github.com/dlang/dmd/pull/17050#issuecomment-2543370370
struct MutableString(size_t E) {}
struct StringTest
{
alias toString this;
const(char)[] toString() const pure
=> null;
this(StringTest rhs) {}
this(ref inout StringTest rhs) inout pure {}
this(typeof(null)) inout pure {}
this(size_t Embed)(MutableString!Embed str) inout {}
~this() {}
}
void test7()
{
StringTest s = StringTest(null);
}
/********************************/
int main()
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
return 0;
}

View file

@ -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;
}

View file

@ -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"),