| /** |
| * 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
|