blob: 21f5cc76a4b63cfb3985794b78a884148c6296a6 [file] [log] [blame]
/**
* Defines the bulk of the classes which represent the AST at the expression level.
*
* Specification: ($LINK2 https://dlang.org/spec/expression.html, Expressions)
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/expression.d, _expression.d)
* Documentation: https://dlang.org/phobos/dmd_expression.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/expression.d
*/
module dmd.expression;
import core.stdc.stdarg;
import core.stdc.stdio;
import core.stdc.string;
import dmd.aggregate;
import dmd.aliasthis;
import dmd.apply;
import dmd.arrayop;
import dmd.arraytypes;
import dmd.astenums;
import dmd.ast_node;
import dmd.gluelayer;
import dmd.constfold;
import dmd.ctfeexpr;
import dmd.ctorflow;
import dmd.dcast;
import dmd.dclass;
import dmd.declaration;
import dmd.delegatize;
import dmd.dimport;
import dmd.dinterpret;
import dmd.dmodule;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.errors;
import dmd.escape;
import dmd.expressionsem;
import dmd.func;
import dmd.globals;
import dmd.hdrgen;
import dmd.id;
import dmd.identifier;
import dmd.init;
import dmd.inline;
import dmd.mtype;
import dmd.nspace;
import dmd.objc;
import dmd.opover;
import dmd.optimize;
import dmd.root.complex;
import dmd.root.ctfloat;
import dmd.root.filename;
import dmd.common.outbuffer;
import dmd.root.optional;
import dmd.root.rmem;
import dmd.root.rootobject;
import dmd.root.string;
import dmd.root.utf;
import dmd.safe;
import dmd.sideeffect;
import dmd.target;
import dmd.tokens;
import dmd.typesem;
import dmd.visitor;
enum LOGSEMANTIC = false;
void emplaceExp(T : Expression, Args...)(void* p, Args args)
{
static if (__VERSION__ < 2099)
const init = typeid(T).initializer;
else
const init = __traits(initSymbol, T);
p[0 .. __traits(classInstanceSize, T)] = init[];
(cast(T)p).__ctor(args);
}
void emplaceExp(T : UnionExp)(T* p, Expression e)
{
memcpy(p, cast(void*)e, e.size);
}
/// Return value for `checkModifiable`
enum Modifiable
{
/// Not modifiable
no,
/// Modifiable (the type is mutable)
yes,
/// Modifiable because it is initialization
initialization,
}
/**
* Specifies how the checkModify deals with certain situations
*/
enum ModifyFlags
{
/// Issue error messages on invalid modifications of the variable
none,
/// No errors are emitted for invalid modifications
noError = 0x1,
/// The modification occurs for a subfield of the current variable
fieldAssign = 0x2,
}
/****************************************
* Find the first non-comma expression.
* Params:
* e = Expressions connected by commas
* Returns:
* left-most non-comma expression
*/
inout(Expression) firstComma(inout Expression e)
{
Expression ex = cast()e;
while (ex.op == EXP.comma)
ex = (cast(CommaExp)ex).e1;
return cast(inout)ex;
}
/****************************************
* Find the last non-comma expression.
* Params:
* e = Expressions connected by commas
* Returns:
* right-most non-comma expression
*/
inout(Expression) lastComma(inout Expression e)
{
Expression ex = cast()e;
while (ex.op == EXP.comma)
ex = (cast(CommaExp)ex).e2;
return cast(inout)ex;
}
/*****************************************
* Determine if `this` is available by walking up the enclosing
* scopes until a function is found.
*
* Params:
* sc = where to start looking for the enclosing function
* Returns:
* Found function if it satisfies `isThis()`, otherwise `null`
*/
FuncDeclaration hasThis(Scope* sc)
{
//printf("hasThis()\n");
Dsymbol p = sc.parent;
while (p && p.isTemplateMixin())
p = p.parent;
FuncDeclaration fdthis = p ? p.isFuncDeclaration() : null;
//printf("fdthis = %p, '%s'\n", fdthis, fdthis ? fdthis.toChars() : "");
// Go upwards until we find the enclosing member function
FuncDeclaration fd = fdthis;
while (1)
{
if (!fd)
{
return null;
}
if (!fd.isNested() || fd.isThis() || (fd.hasDualContext() && fd.isMember2()))
break;
Dsymbol parent = fd.parent;
while (1)
{
if (!parent)
return null;
TemplateInstance ti = parent.isTemplateInstance();
if (ti)
parent = ti.parent;
else
break;
}
fd = parent.isFuncDeclaration();
}
if (!fd.isThis() && !(fd.hasDualContext() && fd.isMember2()))
{
return null;
}
assert(fd.vthis);
return fd;
}
/***********************************
* Determine if a `this` is needed to access `d`.
* Params:
* sc = context
* d = declaration to check
* Returns:
* true means a `this` is needed
*/
bool isNeedThisScope(Scope* sc, Declaration d)
{
if (sc.intypeof == 1)
return false;
AggregateDeclaration ad = d.isThis();
if (!ad)
return false;
//printf("d = %s, ad = %s\n", d.toChars(), ad.toChars());
for (Dsymbol s = sc.parent; s; s = s.toParentLocal())
{
//printf("\ts = %s %s, toParent2() = %p\n", s.kind(), s.toChars(), s.toParent2());
if (AggregateDeclaration ad2 = s.isAggregateDeclaration())
{
if (ad2 == ad)
return false;
else if (ad2.isNested())
continue;
else
return true;
}
if (FuncDeclaration f = s.isFuncDeclaration())
{
if (f.isMemberLocal())
break;
}
}
return true;
}
/******************************
* check e is exp.opDispatch!(tiargs) or not
* It's used to switch to UFCS the semantic analysis path
*/
bool isDotOpDispatch(Expression e)
{
if (auto dtie = e.isDotTemplateInstanceExp())
return dtie.ti.name == Id.opDispatch;
return false;
}
/****************************************
* Expand tuples.
* Input:
* exps aray of Expressions
* Output:
* exps rewritten in place
*/
extern (C++) void expandTuples(Expressions* exps)
{
//printf("expandTuples()\n");
if (exps is null)
return;
for (size_t i = 0; i < exps.dim; i++)
{
Expression arg = (*exps)[i];
if (!arg)
continue;
// Look for tuple with 0 members
if (auto e = arg.isTypeExp())
{
if (auto tt = e.type.toBasetype().isTypeTuple())
{
if (!tt.arguments || tt.arguments.dim == 0)
{
exps.remove(i);
if (i == exps.dim)
return;
}
else // Expand a TypeTuple
{
exps.remove(i);
auto texps = new Expressions(tt.arguments.length);
foreach (j, a; *tt.arguments)
(*texps)[j] = new TypeExp(e.loc, a.type);
exps.insert(i, texps);
}
i--;
continue;
}
}
// Inline expand all the tuples
while (arg.op == EXP.tuple)
{
TupleExp te = cast(TupleExp)arg;
exps.remove(i); // remove arg
exps.insert(i, te.exps); // replace with tuple contents
if (i == exps.dim)
return; // empty tuple, no more arguments
(*exps)[i] = Expression.combine(te.e0, (*exps)[i]);
arg = (*exps)[i];
}
}
}
/****************************************
* Expand alias this tuples.
*/
TupleDeclaration isAliasThisTuple(Expression e)
{
if (!e.type)
return null;
Type t = e.type.toBasetype();
while (true)
{
if (Dsymbol s = t.toDsymbol(null))
{
if (auto ad = s.isAggregateDeclaration())
{
s = ad.aliasthis ? ad.aliasthis.sym : null;
if (s && s.isVarDeclaration())
{
TupleDeclaration td = s.isVarDeclaration().toAlias().isTupleDeclaration();
if (td && td.isexp)
return td;
}
if (Type att = t.aliasthisOf())
{
t = att;
continue;
}
}
}
return null;
}
}
int expandAliasThisTuples(Expressions* exps, size_t starti = 0)
{
if (!exps || exps.dim == 0)
return -1;
for (size_t u = starti; u < exps.dim; u++)
{
Expression exp = (*exps)[u];
if (TupleDeclaration td = exp.isAliasThisTuple)
{
exps.remove(u);
size_t i;
td.foreachVar((s)
{
auto d = s.isDeclaration();
auto e = new DotVarExp(exp.loc, exp, d);
assert(d.type);
e.type = d.type;
exps.insert(u + i, e);
++i;
});
version (none)
{
printf("expansion ->\n");
foreach (e; exps)
{
printf("\texps[%d] e = %s %s\n", i, EXPtoString(e.op), e.toChars());
}
}
return cast(int)u;
}
}
return -1;
}
/****************************************
* If `s` is a function template, i.e. the only member of a template
* and that member is a function, return that template.
* Params:
* s = symbol that might be a function template
* Returns:
* template for that function, otherwise null
*/
TemplateDeclaration getFuncTemplateDecl(Dsymbol s)
{
FuncDeclaration f = s.isFuncDeclaration();
if (f && f.parent)
{
if (auto ti = f.parent.isTemplateInstance())
{
if (!ti.isTemplateMixin() && ti.tempdecl)
{
auto td = ti.tempdecl.isTemplateDeclaration();
if (td.onemember && td.ident == f.ident)
{
return td;
}
}
}
}
return null;
}
/************************************************
* If we want the value of this expression, but do not want to call
* the destructor on it.
*/
Expression valueNoDtor(Expression e)
{
auto ex = lastComma(e);
if (auto ce = ex.isCallExp())
{
/* The struct value returned from the function is transferred
* so do not call the destructor on it.
* Recognize:
* ((S _ctmp = S.init), _ctmp).this(...)
* and make sure the destructor is not called on _ctmp
* BUG: if ex is a CommaExp, we should go down the right side.
*/
if (auto dve = ce.e1.isDotVarExp())
{
if (dve.var.isCtorDeclaration())
{
// It's a constructor call
if (auto comma = dve.e1.isCommaExp())
{
if (auto ve = comma.e2.isVarExp())
{
VarDeclaration ctmp = ve.var.isVarDeclaration();
if (ctmp)
{
ctmp.storage_class |= STC.nodtor;
assert(!ce.isLvalue());
}
}
}
}
}
}
else if (auto ve = ex.isVarExp())
{
auto vtmp = ve.var.isVarDeclaration();
if (vtmp && (vtmp.storage_class & STC.rvalue))
{
vtmp.storage_class |= STC.nodtor;
}
}
return e;
}
/*********************************************
* If e is an instance of a struct, and that struct has a copy constructor,
* rewrite e as:
* (tmp = e),tmp
* Input:
* sc = just used to specify the scope of created temporary variable
* destinationType = the type of the object on which the copy constructor is called;
* may be null if the struct defines a postblit
*/
private Expression callCpCtor(Scope* sc, Expression e, Type destinationType)
{
if (auto ts = e.type.baseElemOf().isTypeStruct())
{
StructDeclaration sd = ts.sym;
if (sd.postblit || sd.hasCopyCtor)
{
/* Create a variable tmp, and replace the argument e with:
* (tmp = e),tmp
* and let AssignExp() handle the construction.
* This is not the most efficient, ideally tmp would be constructed
* directly onto the stack.
*/
auto tmp = copyToTemp(STC.rvalue, "__copytmp", e);
if (sd.hasCopyCtor && destinationType)
{
// https://issues.dlang.org/show_bug.cgi?id=22619
// If the destination type is inout we can preserve it
// only if inside an inout function; if we are not inside
// an inout function, then we will preserve the type of
// the source
if (destinationType.hasWild && !(sc.func.storage_class & STC.wild))
tmp.type = e.type;
else
tmp.type = destinationType;
}
tmp.storage_class |= STC.nodtor;
tmp.dsymbolSemantic(sc);
Expression de = new DeclarationExp(e.loc, tmp);
Expression ve = new VarExp(e.loc, tmp);
de.type = Type.tvoid;
ve.type = e.type;
return Expression.combine(de, ve);
}
}
return e;
}
/************************************************
* Handle the postblit call on lvalue, or the move of rvalue.
*
* Params:
* sc = the scope where the expression is encountered
* e = the expression the needs to be moved or copied (source)
* t = if the struct defines a copy constructor, the type of the destination
*
* Returns:
* The expression that copy constructs or moves the value.
*/
extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null)
{
if (auto ce = e.isCondExp())
{
ce.e1 = doCopyOrMove(sc, ce.e1);
ce.e2 = doCopyOrMove(sc, ce.e2);
}
else
{
e = e.isLvalue() ? callCpCtor(sc, e, t) : valueNoDtor(e);
}
return e;
}
/****************************************************************/
/* A type meant as a union of all the Expression types,
* to serve essentially as a Variant that will sit on the stack
* during CTFE to reduce memory consumption.
*/
extern (C++) struct UnionExp
{
// yes, default constructor does nothing
extern (D) this(Expression e)
{
memcpy(&this, cast(void*)e, e.size);
}
/* Extract pointer to Expression
*/
extern (C++) Expression exp() return
{
return cast(Expression)&u;
}
/* Convert to an allocated Expression
*/
extern (C++) Expression copy()
{
Expression e = exp();
//if (e.size > sizeof(u)) printf("%s\n", EXPtoString(e.op).ptr);
assert(e.size <= u.sizeof);
switch (e.op)
{
case EXP.cantExpression: return CTFEExp.cantexp;
case EXP.voidExpression: return CTFEExp.voidexp;
case EXP.break_: return CTFEExp.breakexp;
case EXP.continue_: return CTFEExp.continueexp;
case EXP.goto_: return CTFEExp.gotoexp;
default: return e.copy();
}
}
private:
// Ensure that the union is suitably aligned.
align(8) union __AnonStruct__u
{
char[__traits(classInstanceSize, Expression)] exp;
char[__traits(classInstanceSize, IntegerExp)] integerexp;
char[__traits(classInstanceSize, ErrorExp)] errorexp;
char[__traits(classInstanceSize, RealExp)] realexp;
char[__traits(classInstanceSize, ComplexExp)] complexexp;
char[__traits(classInstanceSize, SymOffExp)] symoffexp;
char[__traits(classInstanceSize, StringExp)] stringexp;
char[__traits(classInstanceSize, ArrayLiteralExp)] arrayliteralexp;
char[__traits(classInstanceSize, AssocArrayLiteralExp)] assocarrayliteralexp;
char[__traits(classInstanceSize, StructLiteralExp)] structliteralexp;
char[__traits(classInstanceSize, CompoundLiteralExp)] compoundliteralexp;
char[__traits(classInstanceSize, NullExp)] nullexp;
char[__traits(classInstanceSize, DotVarExp)] dotvarexp;
char[__traits(classInstanceSize, AddrExp)] addrexp;
char[__traits(classInstanceSize, IndexExp)] indexexp;
char[__traits(classInstanceSize, SliceExp)] sliceexp;
char[__traits(classInstanceSize, VectorExp)] vectorexp;
}
__AnonStruct__u u;
}
/********************************
* Test to see if two reals are the same.
* Regard NaN's as equivalent.
* Regard +0 and -0 as different.
* Params:
* x1 = first operand
* x2 = second operand
* Returns:
* true if x1 is x2
* else false
*/
bool RealIdentical(real_t x1, real_t x2)
{
return (CTFloat.isNaN(x1) && CTFloat.isNaN(x2)) || CTFloat.isIdentical(x1, x2);
}
/************************ TypeDotIdExp ************************************/
/* Things like:
* int.size
* foo.size
* (foo).size
* cast(foo).size
*/
DotIdExp typeDotIdExp(const ref Loc loc, Type type, Identifier ident)
{
return new DotIdExp(loc, new TypeExp(loc, type), ident);
}
/***************************************************
* Given an Expression, find the variable it really is.
*
* For example, `a[index]` is really `a`, and `s.f` is really `s`.
* Params:
* e = Expression to look at
* Returns:
* variable if there is one, null if not
*/
VarDeclaration expToVariable(Expression e)
{
while (1)
{
switch (e.op)
{
case EXP.variable:
return (cast(VarExp)e).var.isVarDeclaration();
case EXP.dotVariable:
e = (cast(DotVarExp)e).e1;
continue;
case EXP.index:
{
IndexExp ei = cast(IndexExp)e;
e = ei.e1;
Type ti = e.type.toBasetype();
if (ti.ty == Tsarray)
continue;
return null;
}
case EXP.slice:
{
SliceExp ei = cast(SliceExp)e;
e = ei.e1;
Type ti = e.type.toBasetype();
if (ti.ty == Tsarray)
continue;
return null;
}
case EXP.this_:
case EXP.super_:
return (cast(ThisExp)e).var.isVarDeclaration();
default:
return null;
}
}
}
enum OwnedBy : ubyte
{
code, // normal code expression in AST
ctfe, // value expression for CTFE
cache, // constant value cached for CTFE
}
enum WANTvalue = 0; // default
enum WANTexpand = 1; // expand const/immutable variables if possible
/***********************************************************
* https://dlang.org/spec/expression.html#expression
*/
extern (C++) abstract class Expression : ASTNode
{
const EXP op; // to minimize use of dynamic_cast
ubyte size; // # of bytes in Expression so we can copy() it
ubyte parens; // if this is a parenthesized expression
Type type; // !=null means that semantic() has been run
Loc loc; // file location
extern (D) this(const ref Loc loc, EXP op, int size)
{
//printf("Expression::Expression(op = %d) this = %p\n", op, this);
this.loc = loc;
this.op = op;
this.size = cast(ubyte)size;
}
static void _init()
{
CTFEExp.cantexp = new CTFEExp(EXP.cantExpression);
CTFEExp.voidexp = new CTFEExp(EXP.voidExpression);
CTFEExp.breakexp = new CTFEExp(EXP.break_);
CTFEExp.continueexp = new CTFEExp(EXP.continue_);
CTFEExp.gotoexp = new CTFEExp(EXP.goto_);
CTFEExp.showcontext = new CTFEExp(EXP.showCtfeContext);
}
/**
* Deinitializes the global state of the compiler.
*
* This can be used to restore the state set by `_init` to its original
* state.
*/
static void deinitialize()
{
CTFEExp.cantexp = CTFEExp.cantexp.init;
CTFEExp.voidexp = CTFEExp.voidexp.init;
CTFEExp.breakexp = CTFEExp.breakexp.init;
CTFEExp.continueexp = CTFEExp.continueexp.init;
CTFEExp.gotoexp = CTFEExp.gotoexp.init;
CTFEExp.showcontext = CTFEExp.showcontext.init;
}
/*********************************
* Does *not* do a deep copy.
*/
final Expression copy()
{
Expression e;
if (!size)
{
debug
{
fprintf(stderr, "No expression copy for: %s\n", toChars());
printf("op = %d\n", op);
}
assert(0);
}
// memory never freed, so can use the faster bump-pointer-allocation
e = cast(Expression)allocmemory(size);
//printf("Expression::copy(op = %d) e = %p\n", op, e);
return cast(Expression)memcpy(cast(void*)e, cast(void*)this, size);
}
Expression syntaxCopy()
{
//printf("Expression::syntaxCopy()\n");
//print();
return copy();
}
// kludge for template.isExpression()
override final DYNCAST dyncast() const
{
return DYNCAST.expression;
}
override const(char)* toChars() const
{
OutBuffer buf;
HdrGenState hgs;
toCBuffer(this, &buf, &hgs);
return buf.extractChars();
}
static if (__VERSION__ < 2092)
{
final void error(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.verror(loc, format, ap);
va_end(ap);
}
}
final void errorSupplemental(const(char)* format, ...)
{
if (type == Type.terror)
return;
va_list ap;
va_start(ap, format);
.verrorSupplemental(loc, format, ap);
va_end(ap);
}
final void warning(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.vwarning(loc, format, ap);
va_end(ap);
}
}
final void deprecation(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.vdeprecation(loc, format, ap);
va_end(ap);
}
}
}
else
{
pragma(printf) final void error(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.verror(loc, format, ap);
va_end(ap);
}
}
pragma(printf) final void errorSupplemental(const(char)* format, ...)
{
if (type == Type.terror)
return;
va_list ap;
va_start(ap, format);
.verrorSupplemental(loc, format, ap);
va_end(ap);
}
pragma(printf) final void warning(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.vwarning(loc, format, ap);
va_end(ap);
}
}
pragma(printf) final void deprecation(const(char)* format, ...) const
{
if (type != Type.terror)
{
va_list ap;
va_start(ap, format);
.vdeprecation(loc, format, ap);
va_end(ap);
}
}
}
/**********************************
* Combine e1 and e2 by CommaExp if both are not NULL.
*/
extern (D) static Expression combine(Expression e1, Expression e2)
{
if (e1)
{
if (e2)
{
e1 = new CommaExp(e1.loc, e1, e2);
e1.type = e2.type;
}
}
else
e1 = e2;
return e1;
}
extern (D) static Expression combine(Expression e1, Expression e2, Expression e3)
{
return combine(combine(e1, e2), e3);
}
extern (D) static Expression combine(Expression e1, Expression e2, Expression e3, Expression e4)
{
return combine(combine(e1, e2), combine(e3, e4));
}
/**********************************
* If 'e' is a tree of commas, returns the rightmost expression
* by stripping off it from the tree. The remained part of the tree
* is returned via e0.
* Otherwise 'e' is directly returned and e0 is set to NULL.
*/
extern (D) static Expression extractLast(Expression e, out Expression e0)
{
if (e.op != EXP.comma)
{
return e;
}
CommaExp ce = cast(CommaExp)e;
if (ce.e2.op != EXP.comma)
{
e0 = ce.e1;
return ce.e2;
}
else
{
e0 = e;
Expression* pce = &ce.e2;
while ((cast(CommaExp)(*pce)).e2.op == EXP.comma)
{
pce = &(cast(CommaExp)(*pce)).e2;
}
assert((*pce).op == EXP.comma);
ce = cast(CommaExp)(*pce);
*pce = ce.e1;
return ce.e2;
}
}
extern (D) static Expressions* arraySyntaxCopy(Expressions* exps)
{
Expressions* a = null;
if (exps)
{
a = new Expressions(exps.dim);
foreach (i, e; *exps)
{
(*a)[i] = e ? e.syntaxCopy() : null;
}
}
return a;
}
dinteger_t toInteger()
{
//printf("Expression %s\n", EXPtoString(op).ptr);
error("integer constant expression expected instead of `%s`", toChars());
return 0;
}
uinteger_t toUInteger()
{
//printf("Expression %s\n", EXPtoString(op).ptr);
return cast(uinteger_t)toInteger();
}
real_t toReal()
{
error("floating point constant expression expected instead of `%s`", toChars());
return CTFloat.zero;
}
real_t toImaginary()
{
error("floating point constant expression expected instead of `%s`", toChars());
return CTFloat.zero;
}
complex_t toComplex()
{
error("floating point constant expression expected instead of `%s`", toChars());
return complex_t(CTFloat.zero);
}
StringExp toStringExp()
{
return null;
}
/***************************************
* Return !=0 if expression is an lvalue.
*/
bool isLvalue()
{
return false;
}
/*******************************
* Give error if we're not an lvalue.
* If we can, convert expression to be an lvalue.
*/
Expression toLvalue(Scope* sc, Expression e)
{
if (!e)
e = this;
else if (!loc.isValid())
loc = e.loc;
if (e.op == EXP.type)
error("`%s` is a `%s` definition and cannot be modified", e.type.toChars(), e.type.kind());
else
error("`%s` is not an lvalue and cannot be modified", e.toChars());
return ErrorExp.get();
}
Expression modifiableLvalue(Scope* sc, Expression e)
{
//printf("Expression::modifiableLvalue() %s, type = %s\n", toChars(), type.toChars());
// See if this expression is a modifiable lvalue (i.e. not const)
if (checkModifiable(this, sc) == Modifiable.yes)
{
assert(type);
if (!type.isMutable())
{
if (auto dve = this.isDotVarExp())
{
if (isNeedThisScope(sc, dve.var))
for (Dsymbol s = sc.func; s; s = s.toParentLocal())
{
FuncDeclaration ff = s.isFuncDeclaration();
if (!ff)
break;
if (!ff.type.isMutable)
{
error("cannot modify `%s` in `%s` function", toChars(), MODtoChars(type.mod));
return ErrorExp.get();
}
}
}
error("cannot modify `%s` expression `%s`", MODtoChars(type.mod), toChars());
return ErrorExp.get();
}
else if (!type.isAssignable())
{
error("cannot modify struct instance `%s` of type `%s` because it contains `const` or `immutable` members",
toChars(), type.toChars());
return ErrorExp.get();
}
}
return toLvalue(sc, e);
}
final Expression implicitCastTo(Scope* sc, Type t)
{
return .implicitCastTo(this, sc, t);
}
final MATCH implicitConvTo(Type t)
{
return .implicitConvTo(this, t);
}
final Expression castTo(Scope* sc, Type t)
{
return .castTo(this, sc, t);
}
/****************************************
* Resolve __FILE__, __LINE__, __MODULE__, __FUNCTION__, __PRETTY_FUNCTION__, __FILE_FULL_PATH__ to loc.
*/
Expression resolveLoc(const ref Loc loc, Scope* sc)
{
this.loc = loc;
return this;
}
/****************************************
* Check that the expression has a valid type.
* If not, generates an error "... has no type".
* Returns:
* true if the expression is not valid.
* Note:
* When this function returns true, `checkValue()` should also return true.
*/
bool checkType()
{
return false;
}
/****************************************
* Check that the expression has a valid value.
* If not, generates an error "... has no value".
* Returns:
* true if the expression is not valid or has void type.
*/
bool checkValue()
{
if (type && type.toBasetype().ty == Tvoid)
{
error("expression `%s` is `void` and has no value", toChars());
//print(); assert(0);
if (!global.gag)
type = Type.terror;
return true;
}
return false;
}
extern (D) final bool checkScalar()
{
if (op == EXP.error)
return true;
if (type.toBasetype().ty == Terror)
return true;
if (!type.isscalar())
{
error("`%s` is not a scalar, it is a `%s`", toChars(), type.toChars());
return true;
}
return checkValue();
}
extern (D) final bool checkNoBool()
{
if (op == EXP.error)
return true;
if (type.toBasetype().ty == Terror)
return true;
if (type.toBasetype().ty == Tbool)
{
error("operation not allowed on `bool` `%s`", toChars());
return true;
}
return false;
}
extern (D) final bool checkIntegral()
{
if (op == EXP.error)
return true;
if (type.toBasetype().ty == Terror)
return true;
if (!type.isintegral())
{
error("`%s` is not of integral type, it is a `%s`", toChars(), type.toChars());
return true;
}
return checkValue();
}
extern (D) final bool checkArithmetic()
{
if (op == EXP.error)
return true;
if (type.toBasetype().ty == Terror)
return true;
if (!type.isintegral() && !type.isfloating())
{
error("`%s` is not of arithmetic type, it is a `%s`", toChars(), type.toChars());
return true;
}
return checkValue();
}
final bool checkDeprecated(Scope* sc, Dsymbol s)
{
return s.checkDeprecated(loc, sc);
}
extern (D) final bool checkDisabled(Scope* sc, Dsymbol s)
{
if (auto d = s.isDeclaration())
{
return d.checkDisabled(loc, sc);
}
return false;
}
/*********************************************
* Calling function f.
* Check the purity, i.e. if we're in a pure function
* we can only call other pure functions.
* Returns true if error occurs.
*/
extern (D) final bool checkPurity(Scope* sc, FuncDeclaration f)
{
if (!sc.func)
return false;
if (sc.func == f)
return false;
if (sc.intypeof == 1)
return false;
if (sc.flags & (SCOPE.ctfe | SCOPE.debug_))
return false;
// If the call has a pure parent, then the called func must be pure.
if (!f.isPure() && checkImpure(sc))
{
error("`pure` %s `%s` cannot call impure %s `%s`",
sc.func.kind(), sc.func.toPrettyChars(), f.kind(),
f.toPrettyChars());
checkOverridenDtor(sc, f, dd => dd.type.toTypeFunction().purity != PURE.impure, "impure");
return true;
}
return false;
}
/**
* Checks whether `f` is a generated `DtorDeclaration` that hides a user-defined one
* which passes `check` while `f` doesn't (e.g. when the user defined dtor is pure but
* the generated dtor is not).
* In that case the method will identify and print all members causing the attribute
* missmatch.
*
* Params:
* sc = scope
* f = potential `DtorDeclaration`
* check = current check (e.g. whether it's pure)
* checkName = the kind of check (e.g. `"pure"`)
*/
extern (D) final void checkOverridenDtor(Scope* sc, FuncDeclaration f,
scope bool function(DtorDeclaration) check, const string checkName
) {
auto dd = f.isDtorDeclaration();
if (!dd || !dd.isGenerated())
return;
// DtorDeclaration without parents should fail at an earlier stage
auto ad = cast(AggregateDeclaration) f.toParent2();
assert(ad);
if (ad.userDtors.dim)
{
if (!check(ad.userDtors[0])) // doesn't match check (e.g. is impure as well)
return;
// Sanity check
assert(!check(ad.fieldDtor));
}
dd.loc.errorSupplemental("%s`%s.~this` is %.*s because of the following field's destructors:",
dd.isGenerated() ? "generated " : "".ptr,
ad.toChars,
cast(int) checkName.length, checkName.ptr);
// Search for the offending fields
foreach (field; ad.fields)
{
// Only structs may define automatically called destructors
auto ts = field.type.isTypeStruct();
if (!ts)
{
// But they might be part of a static array
auto ta = field.type.isTypeSArray();
if (!ta)
continue;
ts = ta.baseElemOf().isTypeStruct();
if (!ts)
continue;
}
auto fieldSym = ts.toDsymbol(sc);
assert(fieldSym); // Resolving ts must succeed because missing defs. should error before
auto fieldSd = fieldSym.isStructDeclaration();
assert(fieldSd); // ts is a TypeStruct, this would imply a malformed ASR
if (fieldSd.dtor && !check(fieldSd.dtor))
{
field.loc.errorSupplemental(" - %s %s", field.type.toChars(), field.toChars());
if (fieldSd.dtor.isGenerated())
checkOverridenDtor(sc, fieldSd.dtor, check, checkName);
else
fieldSd.dtor.loc.errorSupplemental(" %.*s `%s.~this` is declared here",
cast(int) checkName.length, checkName.ptr, fieldSd.toChars());
}
}
}
/*******************************************
* Accessing variable v.
* Check for purity and safety violations.
* Returns true if error occurs.
*/
extern (D) final bool checkPurity(Scope* sc, VarDeclaration v)
{
//printf("v = %s %s\n", v.type.toChars(), v.toChars());
/* Look for purity and safety violations when accessing variable v
* from current function.
*/
if (!sc.func)
return false;
if (sc.intypeof == 1)
return false; // allow violations inside typeof(expression)
if (sc.flags & (SCOPE.ctfe | SCOPE.debug_))
return false; // allow violations inside compile-time evaluated expressions and debug conditionals
if (v.ident == Id.ctfe)
return false; // magic variable never violates pure and safe
if (v.isImmutable())
return false; // always safe and pure to access immutables...
if (v.isConst() && !v.isReference() && (v.isDataseg() || v.isParameter()) && v.type.implicitConvTo(v.type.immutableOf()))
return false; // or const global/parameter values which have no mutable indirections
if (v.storage_class & STC.manifest)
return false; // ...or manifest constants
// accessing empty structs is pure
if (v.type.ty == Tstruct)
{
StructDeclaration sd = (cast(TypeStruct)v.type).sym;
if (sd.members) // not opaque
{
sd.determineSize(v.loc);
if (sd.hasNoFields)
return false;
}
}
bool err = false;
if (v.isDataseg())
{
// https://issues.dlang.org/show_bug.cgi?id=7533
// Accessing implicit generated __gate is pure.
if (v.ident == Id.gate)
return false;
if (checkImpure(sc))
{
error("`pure` %s `%s` cannot access mutable static data `%s`",
sc.func.kind(), sc.func.toPrettyChars(), v.toChars());
err = true;
}
}
else
{
/* Given:
* void f() {
* int fx;
* pure void g() {
* int gx;
* /+pure+/ void h() {
* int hx;
* /+pure+/ void i() { }
* }
* }
* }
* i() can modify hx and gx but not fx
*/
Dsymbol vparent = v.toParent2();
for (Dsymbol s = sc.func; !err && s; s = s.toParentP(vparent))
{
if (s == vparent)
break;
if (AggregateDeclaration ad = s.isAggregateDeclaration())
{
if (ad.isNested())
continue;
break;
}
FuncDeclaration ff = s.isFuncDeclaration();
if (!ff)
break;
if (ff.isNested() || ff.isThis())
{
if (ff.type.isImmutable() ||
ff.type.isShared() && !MODimplicitConv(ff.type.mod, v.type.mod))
{
OutBuffer ffbuf;
OutBuffer vbuf;
MODMatchToBuffer(&ffbuf, ff.type.mod, v.type.mod);
MODMatchToBuffer(&vbuf, v.type.mod, ff.type.mod);
error("%s%s `%s` cannot access %sdata `%s`",
ffbuf.peekChars(), ff.kind(), ff.toPrettyChars(), vbuf.peekChars(), v.toChars());
err = true;
break;
}
continue;
}
break;
}
}
/* Do not allow safe functions to access __gshared data
*/
if (v.storage_class & STC.gshared)
{
if (sc.setUnsafe(false, this.loc,
"`@safe` function `%s` cannot access `__gshared` data `%s`", sc.func, v))
{
err = true;
}
}
return err;
}
/*
Check if sc.func is impure or can be made impure.
Returns true on error, i.e. if sc.func is pure and cannot be made impure.
*/
private static bool checkImpure(Scope* sc)
{
return sc.func && (sc.flags & SCOPE.compile
? sc.func.isPureBypassingInference() >= PURE.weak
: sc.func.setImpure());
}
/*********************************************
* Calling function f.
* Check the safety, i.e. if we're in a @safe function
* we can only call @safe or @trusted functions.
* Returns true if error occurs.
*/
extern (D) final bool checkSafety(Scope* sc, FuncDeclaration f)
{
if (!sc.func)
return false;
if (sc.func == f)
return false;
if (sc.intypeof == 1)
return false;
if (sc.flags & (SCOPE.ctfe | SCOPE.debug_))
return false;
if (!f.isSafe() && !f.isTrusted())
{
if (sc.flags & SCOPE.compile ? sc.func.isSafeBypassingInference() : sc.func.setUnsafeCall(f))
{
if (!loc.isValid()) // e.g. implicitly generated dtor
loc = sc.func.loc;
const prettyChars = f.toPrettyChars();
error("`@safe` %s `%s` cannot call `@system` %s `%s`",
sc.func.kind(), sc.func.toPrettyChars(), f.kind(),
prettyChars);
f.errorSupplementalInferredSafety(/*max depth*/ 10, /*deprecation*/ false);
.errorSupplemental(f.loc, "`%s` is declared here", prettyChars);
checkOverridenDtor(sc, f, dd => dd.type.toTypeFunction().trust > TRUST.system, "@system");
return true;
}
}
else if (f.isSafe() && f.safetyViolation)
{
// for dip1000 by default transition, print deprecations for calling functions that will become `@system`
if (sc.func.isSafeBypassingInference())
{
.deprecation(this.loc, "`@safe` function `%s` calling `%s`", sc.func.toChars(), f.toChars());
errorSupplementalInferredSafety(f, 10, true);
}
else if (!sc.func.safetyViolation)
{
import dmd.func : AttributeViolation;
sc.func.safetyViolation = new AttributeViolation(this.loc, null, f, null, null);
}
}
return false;
}
/*********************************************
* Calling function f.
* Check the @nogc-ness, i.e. if we're in a @nogc function
* we can only call other @nogc functions.
* Returns true if error occurs.
*/
extern (D) final bool checkNogc(Scope* sc, FuncDeclaration f)
{
if (!sc.func)
return false;
if (sc.func == f)
return false;
if (sc.intypeof == 1)
return false;
if (sc.flags & (SCOPE.ctfe | SCOPE.debug_))
return false;
if (!f.isNogc())
{
if (sc.flags & SCOPE.compile ? sc.func.isNogcBypassingInference() : sc.func.setGC())
{
if (loc.linnum == 0) // e.g. implicitly generated dtor
loc = sc.func.loc;
// Lowered non-@nogc'd hooks will print their own error message inside of nogc.d (NOGCVisitor.visit(CallExp e)),
// so don't print anything to avoid double error messages.
if (!(f.ident == Id._d_HookTraceImpl || f.ident == Id._d_arraysetlengthT
|| f.ident == Id._d_arrayappendT || f.ident == Id._d_arrayappendcTX))
error("`@nogc` %s `%s` cannot call non-@nogc %s `%s`",
sc.func.kind(), sc.func.toPrettyChars(), f.kind(), f.toPrettyChars());
checkOverridenDtor(sc, f, dd => dd.type.toTypeFunction().isnogc, "non-@nogc");
return true;
}
}
return false;
}
/********************************************
* Check that the postblit is callable if t is an array of structs.
* Returns true if error happens.
*/
extern (D) final bool checkPostblit(Scope* sc, Type t)
{
if (auto ts = t.baseElemOf().isTypeStruct())
{
if (global.params.useTypeInfo && Type.dtypeinfo)
{
// https://issues.dlang.org/show_bug.cgi?id=11395
// Require TypeInfo generation for array concatenation
semanticTypeInfo(sc, t);
}
StructDeclaration sd = ts.sym;
if (sd.postblit)
{
if (sd.postblit.checkDisabled(loc, sc))
return true;
//checkDeprecated(sc, sd.postblit); // necessary?
checkPurity(sc, sd.postblit);
checkSafety(sc, sd.postblit);
checkNogc(sc, sd.postblit);
//checkAccess(sd, loc, sc, sd.postblit); // necessary?
return false;
}
}
return false;
}
extern (D) final bool checkRightThis(Scope* sc)
{
if (op == EXP.error)
return true;
if (op == EXP.variable && type.ty != Terror)
{
VarExp ve = cast(VarExp)this;
if (isNeedThisScope(sc, ve.var))
{
//printf("checkRightThis sc.intypeof = %d, ad = %p, func = %p, fdthis = %p\n",
// sc.intypeof, sc.getStructClassScope(), func, fdthis);
error("need `this` for `%s` of type `%s`", ve.var.toChars(), ve.var.type.toChars());
return true;
}
}
return false;
}
/*******************************
* Check whether the expression allows RMW operations, error with rmw operator diagnostic if not.
* ex is the RHS expression, or NULL if ++/-- is used (for diagnostics)
* Returns true if error occurs.
*/
extern (D) final bool checkReadModifyWrite(EXP rmwOp, Expression ex = null)
{
//printf("Expression::checkReadModifyWrite() %s %s", toChars(), ex ? ex.toChars() : "");
if (!type || !type.isShared() || type.isTypeStruct() || type.isTypeClass())
return false;
// atomicOp uses opAssign (+=/-=) rather than opOp (++/--) for the CT string literal.
switch (rmwOp)
{
case EXP.plusPlus:
case EXP.prePlusPlus:
rmwOp = EXP.addAssign;
break;
case EXP.minusMinus:
case EXP.preMinusMinus:
rmwOp = EXP.minAssign;
break;
default:
break;
}
error("read-modify-write operations are not allowed for `shared` variables");
errorSupplemental("Use `core.atomic.atomicOp!\"%s\"(%s, %s)` instead",
EXPtoString(rmwOp).ptr, toChars(), ex ? ex.toChars() : "1");
return true;
}
/************************************************
* Destructors are attached to VarDeclarations.
* Hence, if expression returns a temp that needs a destructor,
* make sure and create a VarDeclaration for that temp.
*/
Expression addDtorHook(Scope* sc)
{
return this;
}
/******************************
* Take address of expression.
*/
final Expression addressOf()
{
//printf("Expression::addressOf()\n");
debug
{
assert(op == EXP.error || isLvalue());
}
Expression e = new AddrExp(loc, this, type.pointerTo());
return e;
}
/******************************
* If this is a reference, dereference it.
*/
final Expression deref()
{
//printf("Expression::deref()\n");
// type could be null if forward referencing an 'auto' variable
if (type)
if (auto tr = type.isTypeReference())
{
Expression e = new PtrExp(loc, this, tr.next);
return e;
}
return this;
}
final Expression optimize(int result, bool keepLvalue = false)
{
return Expression_optimize(this, result, keepLvalue);
}
// Entry point for CTFE.
// A compile-time result is required. Give an error if not possible
final Expression ctfeInterpret()
{
return .ctfeInterpret(this);
}
final int isConst()
{
return .isConst(this);
}
/// Statically evaluate this expression to a `bool` if possible
/// Returns: an optional thath either contains the value or is empty
Optional!bool toBool()
{
return typeof(return)();
}
bool hasCode()
{
return true;
}
final pure inout nothrow @nogc @safe
{
inout(IntegerExp) isIntegerExp() { return op == EXP.int64 ? cast(typeof(return))this : null; }
inout(ErrorExp) isErrorExp() { return op == EXP.error ? cast(typeof(return))this : null; }
inout(VoidInitExp) isVoidInitExp() { return op == EXP.void_ ? cast(typeof(return))this : null; }
inout(RealExp) isRealExp() { return op == EXP.float64 ? cast(typeof(return))this : null; }
inout(ComplexExp) isComplexExp() { return op == EXP.complex80 ? cast(typeof(return))this : null; }
inout(IdentifierExp) isIdentifierExp() { return op == EXP.identifier ? cast(typeof(return))this : null; }
inout(DollarExp) isDollarExp() { return op == EXP.dollar ? cast(typeof(return))this : null; }
inout(DsymbolExp) isDsymbolExp() { return op == EXP.dSymbol ? cast(typeof(return))this : null; }
inout(ThisExp) isThisExp() { return op == EXP.this_ ? cast(typeof(return))this : null; }
inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; }
inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; }
inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; }
inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; }
inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; }
inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; }
inout(StructLiteralExp) isStructLiteralExp() { return op == EXP.structLiteral ? cast(typeof(return))this : null; }
inout(CompoundLiteralExp) isCompoundLiteralExp() { return op == EXP.compoundLiteral ? cast(typeof(return))this : null; }
inout(TypeExp) isTypeExp() { return op == EXP.type ? cast(typeof(return))this : null; }
inout(ScopeExp) isScopeExp() { return op == EXP.scope_ ? cast(typeof(return))this : null; }
inout(TemplateExp) isTemplateExp() { return op == EXP.template_ ? cast(typeof(return))this : null; }
inout(NewExp) isNewExp() { return op == EXP.new_ ? cast(typeof(return))this : null; }
inout(NewAnonClassExp) isNewAnonClassExp() { return op == EXP.newAnonymousClass ? cast(typeof(return))this : null; }
inout(SymOffExp) isSymOffExp() { return op == EXP.symbolOffset ? cast(typeof(return))this : null; }
inout(VarExp) isVarExp() { return op == EXP.variable ? cast(typeof(return))this : null; }
inout(OverExp) isOverExp() { return op == EXP.overloadSet ? cast(typeof(return))this : null; }
inout(FuncExp) isFuncExp() { return op == EXP.function_ ? cast(typeof(return))this : null; }
inout(DeclarationExp) isDeclarationExp() { return op == EXP.declaration ? cast(typeof(return))this : null; }
inout(TypeidExp) isTypeidExp() { return op == EXP.typeid_ ? cast(typeof(return))this : null; }
inout(TraitsExp) isTraitsExp() { return op == EXP.traits ? cast(typeof(return))this : null; }
inout(HaltExp) isHaltExp() { return op == EXP.halt ? cast(typeof(return))this : null; }
inout(IsExp) isExp() { return op == EXP.is_ ? cast(typeof(return))this : null; }
inout(MixinExp) isMixinExp() { return op == EXP.mixin_ ? cast(typeof(return))this : null; }
inout(ImportExp) isImportExp() { return op == EXP.import_ ? cast(typeof(return))this : null; }
inout(AssertExp) isAssertExp() { return op == EXP.assert_ ? cast(typeof(return))this : null; }
inout(ThrowExp) isThrowExp() { return op == EXP.throw_ ? cast(typeof(return))this : null; }
inout(DotIdExp) isDotIdExp() { return op == EXP.dotIdentifier ? cast(typeof(return))this : null; }
inout(DotTemplateExp) isDotTemplateExp() { return op == EXP.dotTemplateDeclaration ? cast(typeof(return))this : null; }
inout(DotVarExp) isDotVarExp() { return op == EXP.dotVariable ? cast(typeof(return))this : null; }
inout(DotTemplateInstanceExp) isDotTemplateInstanceExp() { return op == EXP.dotTemplateInstance ? cast(typeof(return))this : null; }
inout(DelegateExp) isDelegateExp() { return op == EXP.delegate_ ? cast(typeof(return))this : null; }
inout(DotTypeExp) isDotTypeExp() { return op == EXP.dotType ? cast(typeof(return))this : null; }
inout(CallExp) isCallExp() { return op == EXP.call ? cast(typeof(return))this : null; }
inout(AddrExp) isAddrExp() { return op == EXP.address ? cast(typeof(return))this : null; }
inout(PtrExp) isPtrExp() { return op == EXP.star ? cast(typeof(return))this : null; }
inout(NegExp) isNegExp() { return op == EXP.negate ? cast(typeof(return))this : null; }
inout(UAddExp) isUAddExp() { return op == EXP.uadd ? cast(typeof(return))this : null; }
inout(ComExp) isComExp() { return op == EXP.tilde ? cast(typeof(return))this : null; }
inout(NotExp) isNotExp() { return op == EXP.not ? cast(typeof(return))this : null; }
inout(DeleteExp) isDeleteExp() { return op == EXP.delete_ ? cast(typeof(return))this : null; }
inout(CastExp) isCastExp() { return op == EXP.cast_ ? cast(typeof(return))this : null; }
inout(VectorExp) isVectorExp() { return op == EXP.vector ? cast(typeof(return))this : null; }
inout(VectorArrayExp) isVectorArrayExp() { return op == EXP.vectorArray ? cast(typeof(return))this : null; }
inout(SliceExp) isSliceExp() { return op == EXP.slice ? cast(typeof(return))this : null; }
inout(ArrayLengthExp) isArrayLengthExp() { return op == EXP.arrayLength ? cast(typeof(return))this : null; }
inout(ArrayExp) isArrayExp() { return op == EXP.array ? cast(typeof(return))this : null; }
inout(DotExp) isDotExp() { return op == EXP.dot ? cast(typeof(return))this : null; }
inout(CommaExp) isCommaExp() { return op == EXP.comma ? cast(typeof(return))this : null; }
inout(IntervalExp) isIntervalExp() { return op == EXP.interval ? cast(typeof(return))this : null; }
inout(DelegatePtrExp) isDelegatePtrExp() { return op == EXP.delegatePointer ? cast(typeof(return))this : null; }
inout(DelegateFuncptrExp) isDelegateFuncptrExp() { return op == EXP.delegateFunctionPointer ? cast(typeof(return))this : null; }
inout(IndexExp) isIndexExp() { return op == EXP.index ? cast(typeof(return))this : null; }
inout(PostExp) isPostExp() { return (op == EXP.plusPlus || op == EXP.minusMinus) ? cast(typeof(return))this : null; }
inout(PreExp) isPreExp() { return (op == EXP.prePlusPlus || op == EXP.preMinusMinus) ? cast(typeof(return))this : null; }
inout(AssignExp) isAssignExp() { return op == EXP.assign ? cast(typeof(return))this : null; }
inout(ConstructExp) isConstructExp() { return op == EXP.construct ? cast(typeof(return))this : null; }
inout(BlitExp) isBlitExp() { return op == EXP.blit ? cast(typeof(return))this : null; }
inout(AddAssignExp) isAddAssignExp() { return op == EXP.addAssign ? cast(typeof(return))this : null; }
inout(MinAssignExp) isMinAssignExp() { return op == EXP.minAssign ? cast(typeof(return))this : null; }
inout(MulAssignExp) isMulAssignExp() { return op == EXP.mulAssign ? cast(typeof(return))this : null; }
inout(DivAssignExp) isDivAssignExp() { return op == EXP.divAssign ? cast(typeof(return))this : null; }
inout(ModAssignExp) isModAssignExp() { return op == EXP.modAssign ? cast(typeof(return))this : null; }
inout(AndAssignExp) isAndAssignExp() { return op == EXP.andAssign ? cast(typeof(return))this : null; }
inout(OrAssignExp) isOrAssignExp() { return op == EXP.orAssign ? cast(typeof(return))this : null; }
inout(XorAssignExp) isXorAssignExp() { return op == EXP.xorAssign ? cast(typeof(return))this : null; }
inout(PowAssignExp) isPowAssignExp() { return op == EXP.powAssign ? cast(typeof(return))this : null; }
inout(ShlAssignExp) isShlAssignExp() { return op == EXP.leftShiftAssign ? cast(typeof(return))this : null; }
inout(ShrAssignExp) isShrAssignExp() { return op == EXP.rightShiftAssign ? cast(typeof(return))this : null; }
inout(UshrAssignExp) isUshrAssignExp() { return op == EXP.unsignedRightShiftAssign ? cast(typeof(return))this : null; }
inout(CatAssignExp) isCatAssignExp() { return op == EXP.concatenateAssign
? cast(typeof(return))this
: null; }
inout(CatElemAssignExp) isCatElemAssignExp() { return op == EXP.concatenateElemAssign
? cast(typeof(return))this
: null; }
inout(CatDcharAssignExp) isCatDcharAssignExp() { return op == EXP.concatenateDcharAssign
? cast(typeof(return))this
: null; }
inout(AddExp) isAddExp() { return op == EXP.add ? cast(typeof(return))this : null; }
inout(MinExp) isMinExp() { return op == EXP.min ? cast(typeof(return))this : null; }
inout(CatExp) isCatExp() { return op == EXP.concatenate ? cast(typeof(return))this : null; }
inout(MulExp) isMulExp() { return op == EXP.mul ? cast(typeof(return))this : null; }
inout(DivExp) isDivExp() { return op == EXP.div ? cast(typeof(return))this : null; }
inout(ModExp) isModExp() { return op == EXP.mod ? cast(typeof(return))this : null; }
inout(PowExp) isPowExp() { return op == EXP.pow ? cast(typeof(return))this : null; }
inout(ShlExp) isShlExp() { return op == EXP.leftShift ? cast(typeof(return))this : null; }
inout(ShrExp) isShrExp() { return op == EXP.rightShift ? cast(typeof(return))this : null; }
inout(UshrExp) isUshrExp() { return op == EXP.unsignedRightShift ? cast(typeof(return))this : null; }
inout(AndExp) isAndExp() { return op == EXP.and ? cast(typeof(return))this : null; }
inout(OrExp) isOrExp() { return op == EXP.or ? cast(typeof(return))this : null; }
inout(XorExp) isXorExp() { return op == EXP.xor ? cast(typeof(return))this : null; }
inout(LogicalExp) isLogicalExp() { return (op == EXP.andAnd || op == EXP.orOr) ? cast(typeof(return))this : null; }
//inout(CmpExp) isCmpExp() { return op == EXP. ? cast(typeof(return))this : null; }
inout(InExp) isInExp() { return op == EXP.in_ ? cast(typeof(return))this : null; }
inout(RemoveExp) isRemoveExp() { return op == EXP.remove ? cast(typeof(return))this : null; }
inout(EqualExp) isEqualExp() { return (op == EXP.equal || op == EXP.notEqual) ? cast(typeof(return))this : null; }
inout(IdentityExp) isIdentityExp() { return (op == EXP.identity || op == EXP.notIdentity) ? cast(typeof(return))this : null; }
inout(CondExp) isCondExp() { return op == EXP.question ? cast(typeof(return))this : null; }
inout(GenericExp) isGenericExp() { return op == EXP._Generic ? cast(typeof(return))this : null; }
inout(DefaultInitExp) isDefaultInitExp() { return isDefaultInitOp(op) ? cast(typeof(return))this: null; }
inout(FileInitExp) isFileInitExp() { return (op == EXP.file || op == EXP.fileFullPath) ? cast(typeof(return))this : null; }
inout(LineInitExp) isLineInitExp() { return op == EXP.line ? cast(typeof(return))this : null; }
inout(ModuleInitExp) isModuleInitExp() { return op == EXP.moduleString ? cast(typeof(return))this : null; }
inout(FuncInitExp) isFuncInitExp() { return op == EXP.functionString ? cast(typeof(return))this : null; }
inout(PrettyFuncInitExp) isPrettyFuncInitExp() { return op == EXP.prettyFunction ? cast(typeof(return))this : null; }
inout(ObjcClassReferenceExp) isObjcClassReferenceExp() { return op == EXP.objcClassReference ? cast(typeof(return))this : null; }
inout(ClassReferenceExp) isClassReferenceExp() { return op == EXP.classReference ? cast(typeof(return))this : null; }
inout(ThrownExceptionExp) isThrownExceptionExp() { return op == EXP.thrownException ? cast(typeof(return))this : null; }
inout(UnaExp) isUnaExp() pure inout nothrow @nogc
{
return exptab[op] & EXPFLAGS.unary ? cast(typeof(return))this : null;
}
inout(BinExp) isBinExp() pure inout nothrow @nogc
{
return exptab[op] & EXPFLAGS.binary ? cast(typeof(return))this : null;
}
inout(BinAssignExp) isBinAssignExp() pure inout nothrow @nogc
{
return exptab[op] & EXPFLAGS.binaryAssign ? cast(typeof(return))this : null;
}
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* A compile-time known integer value
*/
extern (C++) final class IntegerExp : Expression
{
private dinteger_t value;
extern (D) this(const ref Loc loc, dinteger_t value, Type type)
{
super(loc, EXP.int64, __traits(classInstanceSize, IntegerExp));
//printf("IntegerExp(value = %lld, type = '%s')\n", value, type ? type.toChars() : "");
assert(type);
if (!type.isscalar())
{
//printf("%s, loc = %d\n", toChars(), loc.linnum);
if (type.ty != Terror)
error("integral constant must be scalar type, not `%s`", type.toChars());
type = Type.terror;
}
this.type = type;
this.value = normalize(type.toBasetype().ty, value);
}
extern (D) this(dinteger_t value)
{
super(Loc.initial, EXP.int64, __traits(classInstanceSize, IntegerExp));
this.type = Type.tint32;
this.value = cast(int)value;
}
static IntegerExp create(const ref Loc loc, dinteger_t value, Type type)
{
return new IntegerExp(loc, value, type);
}
// Same as create, but doesn't allocate memory.
static void emplace(UnionExp* pue, const ref Loc loc, dinteger_t value, Type type)
{
emplaceExp!(IntegerExp)(pue, loc, value, type);
}
override bool equals(const RootObject o) const
{
if (this == o)
return true;
if (auto ne = (cast(Expression)o).isIntegerExp())
{
if (type.toHeadMutable().equals(ne.type.toHeadMutable()) && value == ne.value)
{
return true;
}
}
return false;
}
override dinteger_t toInteger()
{
// normalize() is necessary until we fix all the paints of 'type'
return value = normalize(type.toBasetype().ty, value);
}
override real_t toReal()
{
// normalize() is necessary until we fix all the paints of 'type'
const ty = type.toBasetype().ty;
const val = normalize(ty, value);
value = val;
return (ty == Tuns64)
? real_t(cast(ulong)val)
: real_t(cast(long)val);
}
override real_t toImaginary()
{
return CTFloat.zero;
}
override complex_t toComplex()
{
return complex_t(toReal());
}
override Optional!bool toBool()
{
bool r = toInteger() != 0;
return typeof(return)(r);
}
override Expression toLvalue(Scope* sc, Expression e)
{
if (!e)
e = this;
else if (!loc.isValid())
loc = e.loc;
e.error("cannot modify constant `%s`", e.toChars());
return ErrorExp.get();
}
override void accept(Visitor v)
{
v.visit(this);
}
dinteger_t getInteger()
{
return value;
}
void setInteger(dinteger_t value)
{
this.value = normalize(type.toBasetype().ty, value);
}
extern (D) static dinteger_t normalize(TY ty, dinteger_t value)
{
/* 'Normalize' the value of the integer to be in range of the type
*/
dinteger_t result;
switch (ty)
{
case Tbool:
result = (value != 0);
break;
case Tint8:
result = cast(byte)value;
break;
case Tchar:
case Tuns8:
result = cast(ubyte)value;
break;
case Tint16:
result = cast(short)value;
break;
case Twchar:
case Tuns16:
result = cast(ushort)value;
break;
case Tint32:
result = cast(int)value;
break;
case Tdchar:
case Tuns32:
result = cast(uint)value;
break;
case Tint64:
result = cast(long)value;
break;
case Tuns64:
result = cast(ulong)value;
break;
case Tpointer:
if (target.ptrsize == 8)
goto case Tuns64;
if (target.ptrsize == 4)
goto case Tuns32;
if (target.ptrsize == 2)
goto case Tuns16;
assert(0);
default:
break;
}
return result;
}
override IntegerExp syntaxCopy()
{
return this;
}
/**
* Use this instead of creating new instances for commonly used literals
* such as 0 or 1.
*
* Parameters:
* v = The value of the expression
* Returns:
* A static instance of the expression, typed as `Tint32`.
*/
static IntegerExp literal(int v)()
{
__gshared IntegerExp theConstant;
if (!theConstant)
theConstant = new IntegerExp(v);
return theConstant;
}
/**
* Use this instead of creating new instances for commonly used bools.
*
* Parameters:
* b = The value of the expression
* Returns:
* A static instance of the expression, typed as `Type.tbool`.
*/
static IntegerExp createBool(bool b)
{
__gshared IntegerExp trueExp, falseExp;
if (!trueExp)
{
trueExp = new IntegerExp(Loc.initial, 1, Type.tbool);
falseExp = new IntegerExp(Loc.initial, 0, Type.tbool);
}
return b ? trueExp : falseExp;
}
}
/***********************************************************
* Use this expression for error recovery.
*
* It should behave as a 'sink' to prevent further cascaded error messages.
*/
extern (C++) final class ErrorExp : Expression
{
private extern (D) this()
{
super(Loc.initial, EXP.error, __traits(classInstanceSize, ErrorExp));
type = Type.terror;
}
static ErrorExp get ()
{
if (errorexp is null)
errorexp = new ErrorExp();
if (global.errors == 0 && global.gaggedErrors == 0)
{
/* Unfortunately, errors can still leak out of gagged errors,
* and we need to set the error count to prevent bogus code
* generation. At least give a message.
*/
.error(Loc.initial, "unknown, please file report on issues.dlang.org");
}
return errorexp;
}
override Expression toLvalue(Scope* sc, Expression e)
{
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
extern (C++) __gshared ErrorExp errorexp; // handy shared value
}
/***********************************************************
* An uninitialized value,
* generated from void initializers.
*
* https://dlang.org/spec/declaration.html#void_init
*/
extern (C++) final class VoidInitExp : Expression
{
VarDeclaration var; /// the variable from where the void value came from, null if not known
/// Useful for error messages
extern (D) this(VarDeclaration var)
{
super(var.loc, EXP.void_, __traits(classInstanceSize, VoidInitExp));
this.var = var;
this.type = var.type;
}
override const(char)* toChars() const
{
return "void";
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* A compile-time known floating point number
*/
extern (C++) final class RealExp : Expression
{
real_t value;
extern (D) this(const ref Loc loc, real_t value, Type type)
{
super(loc, EXP.float64, __traits(classInstanceSize, RealExp));
//printf("RealExp::RealExp(%Lg)\n", value);
this.value = value;
this.type = type;
}
static RealExp create(const ref Loc loc, real_t value, Type type)
{
return new RealExp(loc, value, type);
}
// Same as create, but doesn't allocate memory.
static void emplace(UnionExp* pue, const ref Loc loc, real_t value, Type type)
{
emplaceExp!(RealExp)(pue, loc, value, type);
}
override bool equals(const RootObject o) const
{
if (this == o)
return true;
if (auto ne = (cast(Expression)o).isRealExp())
{
if (type.toHeadMutable().equals(ne.type.toHeadMutable()) && RealIdentical(value, ne.value))
{
return true;
}
}
return false;
}
override dinteger_t toInteger()
{
return cast(sinteger_t)toReal();
}
override uinteger_t toUInteger()
{
return cast(uinteger_t)toReal();
}
override real_t toReal()
{
return type.isreal() ? value : CTFloat.zero;
}
override real_t toImaginary()
{
return type.isreal() ? CTFloat.zero : value;
}
override complex_t toComplex()
{
return complex_t(toReal(), toImaginary());
}
override Optional!bool toBool()
{
return typeof(return)(!!value);
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* A compile-time complex number (deprecated)
*/
extern (C++) final class ComplexExp : Expression
{
complex_t value;
extern (D) this(const ref Loc loc, complex_t value, Type type)
{
super(loc, EXP.complex80, __traits(classInstanceSize, ComplexExp));
this.value = value;
this.type = type;
//printf("ComplexExp::ComplexExp(%s)\n", toChars());
}
static ComplexExp create(const ref Loc loc, complex_t value, Type type)
{
return new ComplexExp(loc, value, type);
}
// Same as create, but doesn't allocate memory.
static void emplace(UnionExp* pue, const ref Loc loc, complex_t value, Type type)
{
emplaceExp!(ComplexExp)(pue, loc, value, type);
}
override bool equals(const RootObject o) const
{
if (this == o)
return true;
if (auto ne = (cast(Expression)o).isComplexExp())
{
if (type.toHeadMutable().equals(ne.type.toHeadMutable()) && RealIdentical(creall(value), creall(ne.value)) && RealIdentical(cimagl(value), cimagl(ne.value)))
{
return true;
}
}
return false;
}
override dinteger_t toInteger()
{
return cast(sinteger_t)toReal();
}
override uinteger_t toUInteger()
{
return cast(uinteger_t)toReal();
}
override real_t toReal()
{
return creall(value);
}
override real_t toImaginary()
{
return cimagl(value);
}
override complex_t toComplex()
{
return value;
}
override Optional!bool toBool()
{
return typeof(return)(!!value);
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* An identifier in the context of an expression (as opposed to a declaration)
*
* ---
* int x; // VarDeclaration with Identifier
* x++; // PostExp with IdentifierExp
* ---
*/
extern (C++) class IdentifierExp : Expression
{
Identifier ident;
extern (D) this(const ref Loc loc, Identifier ident)
{
super(loc, EXP.identifier, __traits(classInstanceSize, IdentifierExp));
this.ident = ident;
}
static IdentifierExp create(const ref Loc loc, Identifier ident)
{
return new IdentifierExp(loc, ident);
}
override final bool isLvalue()
{
return true;
}
override final Expression toLvalue(Scope* sc, Expression e)
{
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* The dollar operator used when indexing or slicing an array. E.g `a[$]`, `a[1 .. $]` etc.
*
* https://dlang.org/spec/arrays.html#array-length
*/
extern (C++) final class DollarExp : IdentifierExp
{
extern (D) this(const ref Loc loc)
{
super(loc, Id.dollar);
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* Won't be generated by parser.
*/
extern (C++) final class DsymbolExp : Expression
{
Dsymbol s;
bool hasOverloads;
extern (D) this(const ref Loc loc, Dsymbol s, bool hasOverloads = true)
{
super(loc, EXP.dSymbol, __traits(classInstanceSize, DsymbolExp));
this.s = s;
this.hasOverloads = hasOverloads;
}
override bool isLvalue()
{
return true;
}
override Expression toLvalue(Scope* sc, Expression e)
{
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* https://dlang.org/spec/expression.html#this
*/
extern (C++) class ThisExp : Expression
{
VarDeclaration var;
extern (D) this(const ref Loc loc)
{
super(loc, EXP.this_, __traits(classInstanceSize, ThisExp));
//printf("ThisExp::ThisExp() loc = %d\n", loc.linnum);
}
this(const ref Loc loc, const EXP tok)
{
super(loc, tok, __traits(classInstanceSize, ThisExp));
//printf("ThisExp::ThisExp() loc = %d\n", loc.linnum);
}
override ThisExp syntaxCopy()
{
auto r = cast(ThisExp) super.syntaxCopy();
// require new semantic (possibly new `var` etc.)
r.type = null;
r.var = null;
return r;
}
override Optional!bool toBool()
{
// `this` is never null (what about structs?)
return typeof(return)(true);
}
override final bool isLvalue()
{
// Class `this` should be an rvalue; struct `this` should be an lvalue.
return type.toBasetype().ty != Tclass;
}
override final Expression toLvalue(Scope* sc, Expression e)
{
if (type.toBasetype().ty == Tclass)
{
// Class `this` is an rvalue; struct `this` is an lvalue.
return Expression.toLvalue(sc, e);
}
return this;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* https://dlang.org/spec/expression.html#super
*/
extern (C++) final class SuperExp : ThisExp
{
extern (D) this(const ref Loc loc)
{
super(loc, EXP.super_);
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* A compile-time known `null` value
*
* https://dlang.org/spec/expression.html#null
*/
extern (C++) final class NullExp : Expression
{
extern (D) this(const ref Loc loc, Type type = null)
{
super(loc, EXP.null_, __traits(classInstanceSize, NullExp));
this.type = type;
}
override bool equals(const RootObject o) const
{
if (auto e = o.isExpression())
{
if (e.op == EXP.null_ && type.equals(e.type))
{
return true;
}
}
return false;
}
override Optional!bool toBool()
{
// null in any type is false
return typeof(return)(false);
}
override StringExp toStringExp()
{
if (implicitConvTo(Type.tstring))
{
auto se = new StringExp(loc, (cast(char*)mem.xcalloc(1, 1))[0 .. 0]);
se.type = Type.tstring;
return se;
}
return null;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* https://dlang.org/spec/expression.html#string_literals
*/
extern (C++) final class StringExp : Expression
{
private union
{
char* string; // if sz == 1
wchar* wstring; // if sz == 2
dchar* dstring; // if sz == 4
} // (const if ownedByCtfe == OwnedBy.code)
size_t len; // number of code units
ubyte sz = 1; // 1: char, 2: wchar, 4: dchar
ubyte committed; // !=0 if type is committed
enum char NoPostfix = 0;
char postfix = NoPostfix; // 'c', 'w', 'd'
OwnedBy ownedByCtfe = OwnedBy.code;
extern (D) this(const ref Loc loc, const(void)[] string)
{
super(loc, EXP.string_, __traits(classInstanceSize, StringExp));
this.string = cast(char*)string.ptr; // note that this.string should be const
this.len = string.length;
this.sz = 1; // work around LDC bug #1286
}
extern (D) this(const ref Loc loc, const(void)[] string, size_t len, ubyte sz, char postfix = NoPostfix)
{
super(loc, EXP.string_, __traits(classInstanceSize, StringExp));
this.string = cast(char*)string.ptr; // note that this.string should be const
this.len = len;
this.sz = sz;
this.postfix = postfix;
}
static StringExp create(const ref Loc loc, const(char)* s)
{
return new StringExp(loc, s.toDString());
}
static StringExp create(const ref Loc loc, const(void)* string, size_t len)
{
return new StringExp(loc, string[0 .. len]);
}
// Same as create, but doesn't allocate memory.
static void emplace(UnionExp* pue, const ref Loc loc, const(char)* s)
{
emplaceExp!(StringExp)(pue, loc, s.toDString());
}
extern (D) static void emplace(UnionExp* pue, const ref Loc loc, const(void)[] string)
{
emplaceExp!(StringExp)(pue, loc, string);
}
extern (D) static void emplace(UnionExp* pue, const ref Loc loc, const(void)[] string, size_t len, ubyte sz, char postfix)
{
emplaceExp!(StringExp)(pue, loc, string, len, sz, postfix);
}
override bool equals(const RootObject o) const
{
//printf("StringExp::equals('%s') %s\n", o.toChars(), toChars());
if (auto e = o.isExpression())
{
if (auto se = e.isStringExp())
{
return compare(se) == 0;
}
}
return false;
}
/**********************************
* Return the number of code units the string would be if it were re-encoded
* as tynto.
* Params:
* tynto = code unit type of the target encoding
* Returns:
* number of code units
*/
size_t numberOfCodeUnits(int tynto = 0) const
{
int encSize;
switch (tynto)
{
case 0: return len;
case Tchar: encSize = 1; break;
case Twchar: encSize = 2; break;
case Tdchar: encSize = 4; break;
default:
assert(0);
}
if (sz == encSize)
return len;
size_t result = 0;
dchar c;
switch (sz)
{
case 1:
for (size_t u = 0; u < len;)
{
if (const s = utf_decodeChar(string[0 .. len], u, c))
{
error("%.*s", cast(int)s.length, s.ptr);
return 0;
}
result += utf_codeLength(encSize, c);
}
break;
case 2:
for (size_t u = 0; u < len;)
{
if (const s = utf_decodeWchar(wstring[0 .. len], u, c))
{
error("%.*s", cast(int)s.length, s.ptr);
return 0;
}
result += utf_codeLength(encSize, c);
}
break;
case 4:
foreach (u; 0 .. len)
{
result += utf_codeLength(encSize, dstring[u]);
}
break;
default:
assert(0);
}
return result;
}
/**********************************************
* Write the contents of the string to dest.
* Use numberOfCodeUnits() to determine size of result.
* Params:
* dest = destination
* tyto = encoding type of the result
* zero = add terminating 0
*/
void writeTo(void* dest, bool zero, int tyto = 0) const
{
int encSize;
switch (tyto)
{
case 0: encSize = sz; break;
case Tchar: encSize = 1; break;
case Twchar: encSize = 2; break;
case Tdchar: encSize = 4; break;
default:
assert(0);
}
if (sz == encSize)
{
memcpy(dest, string, len * sz);
if (zero)
memset(dest + len * sz, 0, sz);
}
else
assert(0);
}
/*********************************************
* Get the code unit at index i
* Params:
* i = index
* Returns:
* code unit at index i
*/
dchar getCodeUnit(size_t i) const pure
{
assert(i < len);
final switch (sz)
{
case 1:
return string[i];
case 2:
return wstring[i];
case 4:
return dstring[i];
}
}
/*********************************************
* Set the code unit at index i to c
* Params:
* i = index
* c = code unit to set it to
*/
void setCodeUnit(size_t i, dchar c)
{
assert(i < len);
final switch (sz)
{
case 1:
string[i] = cast(char)c;
break;
case 2:
wstring[i] = cast(wchar)c;
break;
case 4:
dstring[i] = c;
break;
}
}
override StringExp toStringExp()
{
return this;
}
/****************************************
* Convert string to char[].
*/
StringExp toUTF8(Scope* sc)
{
if (sz != 1)
{
// Convert to UTF-8 string
committed = 0;
Expression e = castTo(sc, Type.tchar.arrayOf());
e = e.optimize(WANTvalue);
auto se = e.isStringExp();
assert(se.sz == 1);
return se;
}
return this;
}
/**
* Compare two `StringExp` by length, then value
*
* The comparison is not the usual C-style comparison as seen with
* `strcmp` or `memcmp`, but instead first compare based on the length.
* This allows both faster lookup and sorting when comparing sparse data.
*
* This ordering scheme is relied on by the string-switching feature.
* Code in Druntime's `core.internal.switch_` relies on this ordering
* when doing a binary search among case statements.
*
* Both `StringExp` should be of the same encoding.
*
* Params:
* se2 = String expression to compare `this` to
*
* Returns:
* `0` when `this` is equal to se2, a value greater than `0` if
* `this` should be considered greater than `se2`,
* and a value less than `0` if `this` is lesser than `se2`.
*/
int compare(const StringExp se2) const nothrow pure @nogc
{
//printf("StringExp::compare()\n");
const len1 = len;
const len2 = se2.len;
assert(this.sz == se2.sz, "Comparing string expressions of different sizes");
//printf("sz = %d, len1 = %d, len2 = %d\n", sz, cast(int)len1, cast(int)len2);
if (len1 == len2)
{
switch (sz)
{
case 1:
return memcmp(string, se2.string, len1);
case 2:
{
wchar* s1 = cast(wchar*)string;
wchar* s2 = cast(wchar*)se2.string;
foreach (u; 0 .. len)
{
if (s1[u] != s2[u])
return s1[u] - s2[u];
}
}
break;
case 4:
{
dchar* s1 = cast(dchar*)string;
dchar* s2 = cast(dchar*)se2.string;
foreach (u; 0 .. len)
{
if (s1[u] != s2[u])
return s1[u] - s2[u];
}
}
break;
default:
assert(0);
}
}
return cast(int)(len1 - len2);
}
override Optional!bool toBool()
{
// Keep the old behaviour for this refactoring
// Should probably match language spec instead and check for length
return typeof(return)(true);
}
override bool isLvalue()
{
/* string literal is rvalue in default, but
* conversion to reference of static array is only allowed.
*/
return (type && type.toBasetype().ty == Tsarray);
}
override Expression toLvalue(Scope* sc, Expression e)
{
//printf("StringExp::toLvalue(%s) type = %s\n", toChars(), type ? type.toChars() : NULL);
return (type && type.toBasetype().ty == Tsarray) ? this : Expression.toLvalue(sc, e);
}
override Expression modifiableLvalue(Scope* sc, Expression e)
{
error("cannot modify string literal `%s`", toChars());
return ErrorExp.get();
}
/********************************
* Convert string contents to a 0 terminated string,
* allocated by mem.xmalloc().
*/
extern (D) const(char)[] toStringz() const
{
auto nbytes = len * sz;
char* s = cast(char*)mem.xmalloc(nbytes + sz);
writeTo(s, true);
return s[0 .. nbytes];
}
extern (D) const(char)[] peekString() const
{
assert(sz == 1);
return this.string[0 .. len];
}
extern (D) const(wchar)[] peekWstring() const
{
assert(sz == 2);
return this.wstring[0 .. len];
}
extern (D) const(dchar)[] peekDstring() const
{
assert(sz == 4);
return this.dstring[0 .. len];
}
/*******************
* Get a slice of the data.
*/
extern (D) const(ubyte)[] peekData() const
{
return cast(const(ubyte)[])this.string[0 .. len * sz];
}
/*******************
* Borrow a slice of the data, so the caller can modify
* it in-place (!)
*/
extern (D) ubyte[] borrowData()
{
return cast(ubyte[])this.string[0 .. len * sz];
}
/***********************
* Set new string data.
* `this` becomes the new owner of the data.
*/
extern (D) void setData(void* s, size_t len, ubyte sz)
{
this.string = cast(char*)s;
this.len = len;
this.sz = sz;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* A sequence of expressions
*
* ---
* alias AliasSeq(T...) = T;
* alias Tup = AliasSeq!(3, int, "abc");
* ---
*/
extern (C++) final class TupleExp : Expression
{
/* Tuple-field access may need to take out its side effect part.
* For example:
* foo().tupleof
* is rewritten as:
* (ref __tup