blob: 2830b25d651210af1cb5cd09eb3f0fb3ed8d21a9 [file] [log] [blame]
/**
* Semantic analysis for cast-expressions.
*
* Copyright: Copyright (C) 1999-2023 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/dcast.d, _dcast.d)
* Documentation: https://dlang.org/phobos/dmd_dcast.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dcast.d
*/
module dmd.dcast;
import core.stdc.stdio;
import core.stdc.string;
import dmd.aggregate;
import dmd.aliasthis;
import dmd.arrayop;
import dmd.arraytypes;
import dmd.astenums;
import dmd.dclass;
import dmd.declaration;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.errors;
import dmd.escape;
import dmd.expression;
import dmd.expressionsem;
import dmd.func;
import dmd.globals;
import dmd.hdrgen;
import dmd.location;
import dmd.impcnvtab;
import dmd.id;
import dmd.importc;
import dmd.init;
import dmd.intrange;
import dmd.mtype;
import dmd.opover;
import dmd.root.ctfloat;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.root.utf;
import dmd.tokens;
import dmd.typesem;
import dmd.visitor;
enum LOG = false;
/**
* Attempt to implicitly cast the expression into type `t`.
*
* This routine will change `e`. To check the matching level,
* use `implicitConvTo`.
*
* Params:
* e = Expression that is to be casted
* sc = Current scope
* t = Expected resulting type
*
* Returns:
* The resulting casted expression (mutating `e`), or `ErrorExp`
* if such an implicit conversion is not possible.
*/
Expression implicitCastTo(Expression e, Scope* sc, Type t)
{
Expression visit(Expression e)
{
// printf("Expression.implicitCastTo(%s of type %s) => %s\n", e.toChars(), e.type.toChars(), t.toChars());
if (const match = (sc && sc.flags & SCOPE.Cfile) ? e.cimplicitConvTo(t) : e.implicitConvTo(t))
{
if (match == MATCH.convert && e.type.isTypeNoreturn())
{
return specialNoreturnCast(e, t);
}
if (match == MATCH.constant && (e.type.constConv(t) || !e.isLvalue() && e.type.equivalent(t)))
{
/* Do not emit CastExp for const conversions and
* unique conversions on rvalue.
*/
auto result = e.copy();
result.type = t;
return result;
}
auto ad = isAggregate(e.type);
if (ad && ad.aliasthis)
{
auto ts = ad.type.isTypeStruct();
const adMatch = ts
? ts.implicitConvToWithoutAliasThis(t)
: ad.type.isTypeClass().implicitConvToWithoutAliasThis(t);
if (!adMatch)
{
Type tob = t.toBasetype();
Type t1b = e.type.toBasetype();
if (ad != isAggregate(tob))
{
if (t1b.ty == Tclass && tob.ty == Tclass)
{
ClassDeclaration t1cd = t1b.isClassHandle();
ClassDeclaration tocd = tob.isClassHandle();
int offset;
if (tocd.isBaseOf(t1cd, &offset))
{
auto result = new CastExp(e.loc, e, t);
result.type = t;
return result;
}
}
/* Forward the cast to our alias this member, rewrite to:
* cast(to)e1.aliasthis
*/
auto result = resolveAliasThis(sc, e);
return result.castTo(sc, t);
}
}
}
return e.castTo(sc, t);
}
auto result = e.optimize(WANTvalue);
if (result != e)
{
return implicitCastTo(result, sc, t);
}
if (t.ty != Terror && e.type.ty != Terror)
{
if (!t.deco)
{
e.error("forward reference to type `%s`", t.toChars());
}
else
{
//printf("type %p ty %d deco %p\n", type, type.ty, type.deco);
//type = type.typeSemantic(loc, sc);
//printf("type %s t %s\n", type.deco, t.deco);
auto ts = toAutoQualChars(e.type, t);
e.error("cannot implicitly convert expression `%s` of type `%s` to `%s`",
e.toChars(), ts[0], ts[1]);
}
}
return ErrorExp.get();
}
Expression visitString(StringExp e)
{
//printf("StringExp::implicitCastTo(%s of type %s) => %s\n", e.toChars(), e.type.toChars(), t.toChars());
auto result = visit(e);
if (auto se = result.isStringExp())
{
// Retain polysemous nature if it started out that way
se.committed = e.committed;
}
return result;
}
Expression visitError(ErrorExp e)
{
return e;
}
Expression visitFunc(FuncExp e)
{
//printf("FuncExp::implicitCastTo type = %p %s, t = %s\n", e.type, e.type ? e.type.toChars() : NULL, t.toChars());
FuncExp fe;
if (e.matchType(t, sc, &fe) > MATCH.nomatch)
{
return fe;
}
return visit(e);
}
Expression visitArrayLiteral(ArrayLiteralExp e)
{
auto result = visit(e);
Type tb = result.type.toBasetype();
if (auto ta = tb.isTypeDArray())
if (global.params.useTypeInfo && Type.dtypeinfo)
semanticTypeInfo(sc, ta.next);
return result;
}
Expression visitSlice(SliceExp e)
{
auto result = visit(e);
if (auto se = result.isSliceExp())
if (auto ale = se.e1.isArrayLiteralExp())
{
Type tb = t.toBasetype();
Type tx = (tb.ty == Tsarray)
? tb.nextOf().sarrayOf(ale.elements ? ale.elements.length : 0)
: tb.nextOf().arrayOf();
se.e1 = ale.implicitCastTo(sc, tx);
}
return result;
}
switch (e.op)
{
default : return visit (e);
case EXP.string_ : return visitString (e.isStringExp());
case EXP.error : return visitError (e.isErrorExp());
case EXP.function_ : return visitFunc (e.isFuncExp());
case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp());
case EXP.slice : return visitSlice (e.isSliceExp());
}
}
/**
* Checks whether or not an expression can be implicitly converted
* to type `t`.
*
* Unlike `implicitCastTo`, this routine does not perform the actual cast,
* but only checks up to what `MATCH` level the conversion would be possible.
*
* Params:
* e = Expression that is to be casted
* t = Expected resulting type
*
* Returns:
* The `MATCH` level between `e.type` and `t`.
*/
MATCH implicitConvTo(Expression e, Type t)
{
MATCH visit(Expression e)
{
version (none)
{
printf("Expression::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
//static int nest; if (++nest == 10) assert(0);
if (t == Type.terror)
return MATCH.nomatch;
if (!e.type)
{
e.error("`%s` is not an expression", e.toChars());
e.type = Type.terror;
}
Expression ex = e.optimize(WANTvalue);
if (ex.type.equals(t))
{
return MATCH.exact;
}
if (ex != e)
{
//printf("\toptimized to %s of type %s\n", e.toChars(), e.type.toChars());
return ex.implicitConvTo(t);
}
MATCH match = e.type.implicitConvTo(t);
if (match != MATCH.nomatch)
{
return match;
}
/* See if we can do integral narrowing conversions
*/
if (e.type.isintegral() && t.isintegral() && e.type.isTypeBasic() && t.isTypeBasic())
{
IntRange src = getIntRange(e);
IntRange target = IntRange.fromType(t);
if (target.contains(src))
{
return MATCH.convert;
}
}
return MATCH.nomatch;
}
/******
* Given expression e of type t, see if we can implicitly convert e
* to type tprime, where tprime is type t with mod bits added.
* Returns:
* match level
*/
static MATCH implicitMod(Expression e, Type t, MOD mod)
{
Type tprime;
if (t.ty == Tpointer)
tprime = t.nextOf().castMod(mod).pointerTo();
else if (t.ty == Tarray)
tprime = t.nextOf().castMod(mod).arrayOf();
else if (t.ty == Tsarray)
tprime = t.nextOf().castMod(mod).sarrayOf(t.size() / t.nextOf().size());
else
tprime = t.castMod(mod);
return e.implicitConvTo(tprime);
}
static MATCH implicitConvToAddMin(BinExp e, Type t)
{
/* Is this (ptr +- offset)? If so, then ask ptr
* if the conversion can be done.
* This is to support doing things like implicitly converting a mutable unique
* pointer to an immutable pointer.
*/
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (typeb.ty != Tpointer || tb.ty != Tpointer)
return MATCH.nomatch;
Type t1b = e.e1.type.toBasetype();
Type t2b = e.e2.type.toBasetype();
if (t1b.ty == Tpointer && t2b.isintegral() && t1b.equivalent(tb))
{
// ptr + offset
// ptr - offset
MATCH m = e.e1.implicitConvTo(t);
return (m > MATCH.constant) ? MATCH.constant : m;
}
if (t2b.ty == Tpointer && t1b.isintegral() && t2b.equivalent(tb))
{
// offset + ptr
MATCH m = e.e2.implicitConvTo(t);
return (m > MATCH.constant) ? MATCH.constant : m;
}
return MATCH.nomatch;
}
// Apply mod bits to each function parameter,
// and see if we can convert the function argument to the modded type
static bool parametersModMatch(Expressions* args, TypeFunction tf, MOD mod)
{
const size_t nparams = tf.parameterList.length;
const size_t j = tf.isDstyleVariadic(); // if TypeInfoArray was prepended
foreach (const i; j .. args.length)
{
Expression earg = (*args)[i];
Type targ = earg.type.toBasetype();
static if (LOG)
{
printf("[%d] earg: %s, targ: %s\n", cast(int)i, earg.toChars(), targ.toChars());
}
if (i - j < nparams)
{
Parameter fparam = tf.parameterList[i - j];
if (fparam.isLazy())
return false; // not sure what to do with this
Type tparam = fparam.type;
if (!tparam)
continue;
if (fparam.isReference())
{
if (targ.constConv(tparam.castMod(mod)) == MATCH.nomatch)
return false;
continue;
}
}
static if (LOG)
{
printf("[%d] earg: %s, targm: %s\n", cast(int)i, earg.toChars(), targ.addMod(mod).toChars());
}
if (implicitMod(earg, targ, mod) == MATCH.nomatch)
return false;
}
return true;
}
MATCH visitAdd(AddExp e)
{
version (none)
{
printf("AddExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = visit(e);
if (result == MATCH.nomatch)
result = implicitConvToAddMin(e, t);
return result;
}
MATCH visitMin(MinExp e)
{
version (none)
{
printf("MinExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = visit(e);
if (result == MATCH.nomatch)
result = implicitConvToAddMin(e, t);
return result;
}
MATCH visitInteger(IntegerExp e)
{
version (none)
{
printf("IntegerExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
MATCH m = e.type.implicitConvTo(t);
if (m >= MATCH.constant)
{
return m;
}
TY ty = e.type.toBasetype().ty;
TY toty = t.toBasetype().ty;
TY oldty = ty;
if (m == MATCH.nomatch && t.ty == Tenum)
return MATCH.nomatch;
if (auto tv = t.isTypeVector())
{
TypeBasic tb = tv.elementType();
if (tb.ty == Tvoid)
return MATCH.nomatch;
toty = tb.ty;
}
switch (ty)
{
case Tbool:
case Tint8:
case Tchar:
case Tuns8:
case Tint16:
case Tuns16:
case Twchar:
ty = Tint32;
break;
case Tdchar:
ty = Tuns32;
break;
default:
break;
}
// Only allow conversion if no change in value
immutable dinteger_t value = e.toInteger();
bool isLosslesslyConvertibleToFP(T)()
{
if (e.type.isunsigned())
{
const f = cast(T) value;
return cast(dinteger_t) f == value;
}
const f = cast(T) cast(sinteger_t) value;
return cast(sinteger_t) f == cast(sinteger_t) value;
}
switch (toty)
{
case Tbool:
if ((value & 1) != value)
return MATCH.nomatch;
break;
case Tint8:
if (ty == Tuns64 && value & ~0x7FU)
return MATCH.nomatch;
else if (cast(byte)value != value)
return MATCH.nomatch;
break;
case Tchar:
if ((oldty == Twchar || oldty == Tdchar) && value > 0x7F)
return MATCH.nomatch;
goto case Tuns8;
case Tuns8:
//printf("value = %llu %llu\n", cast(dinteger_t)cast(ubyte)value, value);
if (cast(ubyte)value != value)
return MATCH.nomatch;
break;
case Tint16:
if (ty == Tuns64 && value & ~0x7FFFU)
return MATCH.nomatch;
else if (cast(short)value != value)
return MATCH.nomatch;
break;
case Twchar:
if (oldty == Tdchar && value > 0xD7FF && value < 0xE000)
return MATCH.nomatch;
goto case Tuns16;
case Tuns16:
if (cast(ushort)value != value)
return MATCH.nomatch;
break;
case Tint32:
if (ty == Tuns32)
{
}
else if (ty == Tuns64 && value & ~0x7FFFFFFFU)
return MATCH.nomatch;
else if (cast(int)value != value)
return MATCH.nomatch;
break;
case Tuns32:
if (ty == Tint32)
{
}
else if (cast(uint)value != value)
return MATCH.nomatch;
break;
case Tdchar:
if (value > 0x10FFFFU)
return MATCH.nomatch;
break;
case Tfloat32:
if (!isLosslesslyConvertibleToFP!float)
return MATCH.nomatch;
break;
case Tfloat64:
if (!isLosslesslyConvertibleToFP!double)
return MATCH.nomatch;
break;
case Tfloat80:
if (!isLosslesslyConvertibleToFP!real_t)
return MATCH.nomatch;
break;
case Tpointer:
//printf("type = %s\n", type.toBasetype().toChars());
//printf("t = %s\n", t.toBasetype().toChars());
if (ty == Tpointer && e.type.toBasetype().nextOf().ty == t.toBasetype().nextOf().ty)
{
/* Allow things like:
* const char* P = cast(char *)3;
* char* q = P;
*/
break;
}
goto default;
default:
return visit(e);
}
//printf("MATCH.convert\n");
return MATCH.convert;
}
MATCH visitError(ErrorExp e)
{
return MATCH.nomatch;
}
MATCH visitNull(NullExp e)
{
version (none)
{
printf("NullExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
if (e.type.equals(t))
{
return MATCH.exact;
}
/* Allow implicit conversions from immutable to mutable|const,
* and mutable to immutable. It works because, after all, a null
* doesn't actually point to anything.
*/
if (t.equivalent(e.type))
{
return MATCH.constant;
}
return visit(e);
}
MATCH visitStructLiteral(StructLiteralExp e)
{
version (none)
{
printf("StructLiteralExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
if (e.type.ty == t.ty && e.type.isTypeStruct() && e.type.isTypeStruct().sym == t.isTypeStruct().sym)
{
result = MATCH.constant;
foreach (i, el; (*e.elements)[])
{
if (!el)
continue;
Type te = e.sd.fields[i].type.addMod(t.mod);
MATCH m2 = el.implicitConvTo(te);
//printf("\t%s => %s, match = %d\n", el.toChars(), te.toChars(), m2);
if (m2 < result)
result = m2;
}
}
return result;
}
MATCH visitString(StringExp e)
{
version (none)
{
printf("StringExp::implicitConvTo(this=%s, committed=%d, type=%s, t=%s)\n", e.toChars(), e.committed, e.type.toChars(), t.toChars());
}
if (!e.committed && t.ty == Tpointer && t.nextOf().ty == Tvoid)
return MATCH.nomatch;
if (!(e.type.ty == Tsarray || e.type.ty == Tarray || e.type.ty == Tpointer))
return visit(e);
TY tyn = e.type.nextOf().ty;
if (!tyn.isSomeChar)
return visit(e);
switch (t.ty)
{
case Tsarray:
if (e.type.ty == Tsarray)
{
TY tynto = t.nextOf().ty;
if (tynto == tyn)
{
if (e.type.isTypeSArray().dim.toInteger() == t.isTypeSArray().dim.toInteger())
{
return MATCH.exact;
}
return MATCH.nomatch;
}
if (tynto.isSomeChar)
{
if (e.committed && tynto != tyn)
return MATCH.nomatch;
size_t fromlen = e.numberOfCodeUnits(tynto);
size_t tolen = cast(size_t)t.isTypeSArray().dim.toInteger();
if (tolen < fromlen)
return MATCH.nomatch;
if (tolen != fromlen)
{
// implicit length extending
return MATCH.convert;
}
}
if (!e.committed && tynto.isSomeChar)
{
return MATCH.exact;
}
}
else if (e.type.ty == Tarray)
{
TY tynto = t.nextOf().ty;
if (tynto.isSomeChar)
{
if (e.committed && tynto != tyn)
return MATCH.nomatch;
size_t fromlen = e.numberOfCodeUnits(tynto);
size_t tolen = cast(size_t)t.isTypeSArray().dim.toInteger();
if (tolen < fromlen)
return MATCH.nomatch;
if (tolen != fromlen)
{
// implicit length extending
return MATCH.convert;
}
}
if (tynto == tyn)
{
return MATCH.exact;
}
if (!e.committed && tynto.isSomeChar)
{
return MATCH.exact;
}
}
goto case; /+ fall through +/
case Tarray:
case Tpointer:
Type tn = t.nextOf();
MATCH m = MATCH.exact;
if (e.type.nextOf().mod != tn.mod)
{
// https://issues.dlang.org/show_bug.cgi?id=16183
if (!tn.isConst() && !tn.isImmutable())
return MATCH.nomatch;
m = MATCH.constant;
}
if (!e.committed)
{
switch (tn.ty)
{
case Tchar:
if (e.postfix == 'w' || e.postfix == 'd')
m = MATCH.convert;
return m;
case Twchar:
if (e.postfix != 'w')
m = MATCH.convert;
return m;
case Tdchar:
if (e.postfix != 'd')
m = MATCH.convert;
return m;
case Tenum:
if (tn.isTypeEnum().sym.isSpecial())
{
/* Allow string literal -> const(wchar_t)[]
*/
if (TypeBasic tob = tn.toBasetype().isTypeBasic())
return tn.implicitConvTo(tob);
}
break;
default:
break;
}
}
break;
default:
break;
}
return visit(e);
}
MATCH visitArrayLiteral(ArrayLiteralExp e)
{
version (none)
{
printf("ArrayLiteralExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
auto result = MATCH.nomatch;
if ((tb.ty == Tarray || tb.ty == Tsarray) &&
(typeb.ty == Tarray || typeb.ty == Tsarray))
{
result = MATCH.exact;
Type typen = typeb.nextOf().toBasetype();
if (auto tsa = tb.isTypeSArray())
{
if (e.elements.length != tsa.dim.toInteger())
result = MATCH.nomatch;
}
Type telement = tb.nextOf();
if (!e.elements.length)
{
if (typen.ty != Tvoid)
result = typen.implicitConvTo(telement);
}
else
{
if (e.basis)
{
MATCH m = e.basis.implicitConvTo(telement);
if (m < result)
result = m;
}
for (size_t i = 0; i < e.elements.length; i++)
{
Expression el = (*e.elements)[i];
if (result == MATCH.nomatch)
break;
if (!el)
continue;
MATCH m = el.implicitConvTo(telement);
if (m < result)
result = m; // remember worst match
}
}
if (!result)
result = e.type.implicitConvTo(t);
return result;
}
else if (tb.ty == Tvector && (typeb.ty == Tarray || typeb.ty == Tsarray))
{
result = MATCH.exact;
// Convert array literal to vector type
TypeVector tv = tb.isTypeVector();
TypeSArray tbase = tv.basetype.isTypeSArray();
assert(tbase);
const edim = e.elements.length;
const tbasedim = tbase.dim.toInteger();
if (edim > tbasedim)
{
return MATCH.nomatch;
}
Type telement = tv.elementType();
if (edim < tbasedim)
{
Expression el = typeb.nextOf.defaultInitLiteral(e.loc);
MATCH m = el.implicitConvTo(telement);
if (m < result)
result = m; // remember worst match
}
foreach (el; (*e.elements)[])
{
MATCH m = el.implicitConvTo(telement);
if (m < result)
result = m; // remember worst match
if (result == MATCH.nomatch)
break; // no need to check for worse
}
return result;
}
return visit(e);
}
MATCH visitAssocArrayLiteral(AssocArrayLiteralExp e)
{
auto taa = t.toBasetype().isTypeAArray();
Type typeb = e.type.toBasetype();
if (!(taa && typeb.ty == Taarray))
return visit(e);
auto result = MATCH.exact;
foreach (i, el; (*e.keys)[])
{
MATCH m = el.implicitConvTo(taa.index);
if (m < result)
result = m; // remember worst match
if (result == MATCH.nomatch)
break; // no need to check for worse
el = (*e.values)[i];
m = el.implicitConvTo(taa.nextOf());
if (m < result)
result = m; // remember worst match
if (result == MATCH.nomatch)
break; // no need to check for worse
}
return result;
}
MATCH visitCall(CallExp e)
{
enum LOG = false;
static if (LOG)
{
printf("CallExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
/* Allow the result of strongly pure functions to
* convert to immutable
*/
if (e.f &&
(!global.params.fixImmutableConv || e.f.isPure() >= PURE.const_) &&
e.f.isReturnIsolated() // check isReturnIsolated last, because it is potentially expensive.
)
{
result = e.type.immutableOf().implicitConvTo(t);
if (result > MATCH.constant) // Match level is MATCH.constant at best.
result = MATCH.constant;
return result;
}
/* Conversion is 'const' conversion if:
* 1. function is pure (weakly pure is ok)
* 2. implicit conversion only fails because of mod bits
* 3. each function parameter can be implicitly converted to the mod bits
*/
auto tf = (e.f ? e.f.type : e.e1.type).toBasetype().isTypeFunction();
if (!tf)
return result;
if (tf.purity == PURE.impure)
return result;
if (e.f && e.f.isNested())
return result;
/* See if fail only because of mod bits.
*
* https://issues.dlang.org/show_bug.cgi?id=14155
* All pure functions can access global immutable data.
* So the returned pointer may refer an immutable global data,
* and then the returned pointer that points non-mutable object
* cannot be unique pointer.
*
* Example:
* immutable g;
* static this() { g = 1; }
* const(int*) foo() pure { return &g; }
* void test() {
* immutable(int*) ip = foo(); // OK
* int* mp = foo(); // should be disallowed
* }
*/
if (e.type.immutableOf().implicitConvTo(t) < MATCH.constant && e.type.addMod(MODFlags.shared_).implicitConvTo(t) < MATCH.constant && e.type.implicitConvTo(t.addMod(MODFlags.shared_)) < MATCH.constant)
{
return result;
}
// Allow a conversion to immutable type, or
// conversions of mutable types between thread-local and shared.
/* Get mod bits of what we're converting to
*/
Type tb = t.toBasetype();
MOD mod = tb.mod;
if (tf.isref)
{
}
else
{
if (Type ti = getIndirection(t))
mod = ti.mod;
}
static if (LOG)
{
printf("mod = x%x\n", mod);
}
if (mod & MODFlags.wild)
return result; // not sure what to do with this
/* Apply mod bits to each function parameter,
* and see if we can convert the function argument to the modded type
*/
if (auto dve = e.e1.isDotVarExp())
{
/* Treat 'this' as just another function argument
*/
Type targ = dve.e1.type;
if (targ.constConv(targ.castMod(mod)) == MATCH.nomatch)
return result;
}
if (!parametersModMatch(e.arguments, tf, mod))
return result;
/* Success
*/
return MATCH.constant;
}
MATCH visitAddr(AddrExp e)
{
version (none)
{
printf("AddrExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = e.type.implicitConvTo(t);
//printf("\tresult = %d\n", result);
if (result != MATCH.nomatch)
return result;
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
// Look for pointers to functions where the functions are overloaded.
if (e.e1.op == EXP.overloadSet &&
(tb.ty == Tpointer || tb.ty == Tdelegate) && tb.nextOf().ty == Tfunction)
{
OverExp eo = e.e1.isOverExp();
FuncDeclaration f = null;
foreach (s; eo.vars.a[])
{
FuncDeclaration f2 = s.isFuncDeclaration();
assert(f2);
if (f2.overloadExactMatch(tb.nextOf()))
{
if (f)
{
/* Error if match in more than one overload set,
* even if one is a 'better' match than the other.
*/
ScopeDsymbol.multiplyDefined(e.loc, f, f2);
}
else
f = f2;
result = MATCH.exact;
}
}
}
if (e.e1.op == EXP.variable &&
typeb.ty == Tpointer && typeb.nextOf().ty == Tfunction &&
tb.ty == Tpointer && tb.nextOf().ty == Tfunction)
{
/* I don't think this can ever happen -
* it should have been
* converted to a SymOffExp.
*/
assert(0);
}
//printf("\tresult = %d\n", result);
return result;
}
MATCH visitSymOff(SymOffExp e)
{
version (none)
{
printf("SymOffExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = e.type.implicitConvTo(t);
//printf("\tresult = %d\n", result);
if (result != MATCH.nomatch)
return result;
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
// Look for pointers to functions where the functions are overloaded.
if (typeb.ty == Tpointer && typeb.nextOf().ty == Tfunction &&
(tb.ty == Tpointer || tb.ty == Tdelegate) && tb.nextOf().ty == Tfunction)
{
if (FuncDeclaration f = e.var.isFuncDeclaration())
{
f = f.overloadExactMatch(tb.nextOf());
if (f)
{
if ((tb.ty == Tdelegate && (f.needThis() || f.isNested())) ||
(tb.ty == Tpointer && !(f.needThis() || f.isNested())))
{
result = MATCH.exact;
}
}
}
}
//printf("\tresult = %d\n", result);
return result;
}
MATCH visitDelegate(DelegateExp e)
{
version (none)
{
printf("DelegateExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = e.type.implicitConvTo(t);
if (result != MATCH.nomatch)
return result;
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
// Look for pointers to functions where the functions are overloaded.
if (typeb.ty == Tdelegate && tb.ty == Tdelegate)
{
if (e.func && e.func.overloadExactMatch(tb.nextOf()))
result = MATCH.exact;
}
return result;
}
MATCH visitFunc(FuncExp e)
{
//printf("FuncExp::implicitConvTo type = %p %s, t = %s\n", e.type, e.type ? e.type.toChars() : NULL, t.toChars());
MATCH m = e.matchType(t, null, null, 1);
if (m > MATCH.nomatch)
{
return m;
}
return visit(e);
}
MATCH visitAnd(AndExp e)
{
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
MATCH m1 = e.e1.implicitConvTo(t);
MATCH m2 = e.e2.implicitConvTo(t);
// Pick the worst match
return (m1 < m2) ? m1 : m2;
}
MATCH visitOr(OrExp e)
{
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
MATCH m1 = e.e1.implicitConvTo(t);
MATCH m2 = e.e2.implicitConvTo(t);
// Pick the worst match
return (m1 < m2) ? m1 : m2;
}
MATCH visitXor(XorExp e)
{
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
MATCH m1 = e.e1.implicitConvTo(t);
MATCH m2 = e.e2.implicitConvTo(t);
// Pick the worst match
return (m1 < m2) ? m1 : m2;
}
MATCH visitCond(CondExp e)
{
e.econd = e.econd.optimize(WANTvalue);
const opt = e.econd.toBool();
if (opt.isPresent())
{
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
}
MATCH m1 = e.e1.implicitConvTo(t);
MATCH m2 = e.e2.implicitConvTo(t);
//printf("CondExp: m1 %d m2 %d\n", m1, m2);
// Pick the worst match
return (m1 < m2) ? m1 : m2;
}
MATCH visitComma(CommaExp e)
{
return e.e2.implicitConvTo(t);
}
MATCH visitCast(CastExp e)
{
version (none)
{
printf("CastExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = e.type.implicitConvTo(t);
if (result != MATCH.nomatch)
return result;
if (t.isintegral() && e.e1.type.isintegral() && e.e1.implicitConvTo(t) != MATCH.nomatch)
result = MATCH.convert;
else
result = visit(e);
return result;
}
MATCH visitNew(NewExp e)
{
version (none)
{
printf("NewExp::implicitConvTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
/* Calling new() is like calling a pure function. We can implicitly convert the
* return from new() to t using the same algorithm as in CallExp, with the function
* 'arguments' being:
* thisexp
* arguments
* .init
* 'member' need to be pure.
*/
/* See if fail only because of mod bits
*/
if (e.type.immutableOf().implicitConvTo(t.immutableOf()) == MATCH.nomatch)
return MATCH.nomatch;
/* Get mod bits of what we're converting to
*/
Type tb = t.toBasetype();
MOD mod = tb.mod;
if (Type ti = getIndirection(t))
mod = ti.mod;
static if (LOG)
{
printf("mod = x%x\n", mod);
}
if (mod & MODFlags.wild)
return MATCH.nomatch; // not sure what to do with this
/* Apply mod bits to each argument,
* and see if we can convert the argument to the modded type
*/
if (e.thisexp)
{
/* Treat 'this' as just another function argument
*/
Type targ = e.thisexp.type;
if (targ.constConv(targ.castMod(mod)) == MATCH.nomatch)
return MATCH.nomatch;
}
/* Check call to 'member'
*/
if (e.member)
{
FuncDeclaration fd = e.member;
if (fd.errors || fd.type.ty != Tfunction)
return MATCH.nomatch; // error
TypeFunction tf = fd.type.isTypeFunction();
if (tf.purity == PURE.impure)
return MATCH.nomatch; // impure
// Allow a conversion to immutable type, or
// conversions of mutable types between thread-local and shared.
if (e.type.immutableOf().implicitConvTo(t) < MATCH.constant && e.type.addMod(MODFlags.shared_).implicitConvTo(t) < MATCH.constant && e.type.implicitConvTo(t.addMod(MODFlags.shared_)) < MATCH.constant)
{
return MATCH.nomatch;
}
if (!parametersModMatch(e.arguments, tf, mod))
{
return MATCH.nomatch;
}
}
/* If no 'member', then construction is by simple assignment,
* and just straight check 'arguments'
*/
if (!e.member && e.arguments)
{
for (size_t i = 0; i < e.arguments.length; ++i)
{
Expression earg = (*e.arguments)[i];
if (!earg) // https://issues.dlang.org/show_bug.cgi?id=14853
// if it's on overlapped field
continue;
Type targ = earg.type.toBasetype();
static if (LOG)
{
printf("[%d] earg: %s, targ: %s\n", cast(int)i, earg.toChars(), targ.toChars());
printf("[%d] earg: %s, targm: %s\n", cast(int)i, earg.toChars(), targ.addMod(mod).toChars());
}
if (implicitMod(earg, targ, mod) == MATCH.nomatch)
return MATCH.nomatch;
}
}
/* Consider the .init expression as an argument
*/
Type ntb = e.newtype.toBasetype();
if (ntb.ty == Tarray)
ntb = ntb.nextOf().toBasetype();
if (auto ts = ntb.isTypeStruct())
{
// Don't allow nested structs - uplevel reference may not be convertible
StructDeclaration sd = ts.sym;
sd.size(e.loc); // resolve any forward references
if (sd.isNested())
return MATCH.nomatch;
}
if (ntb.isZeroInit(e.loc))
{
/* Zeros are implicitly convertible, except for special cases.
*/
if (auto tc = ntb.isTypeClass())
{
/* With new() must look at the class instance initializer.
*/
ClassDeclaration cd = tc.sym;
cd.size(e.loc); // resolve any forward references
if (cd.isNested())
return MATCH.nomatch; // uplevel reference may not be convertible
assert(!cd.isInterfaceDeclaration());
struct ClassCheck
{
extern (C++) static bool convertible(Expression e, ClassDeclaration cd, MOD mod)
{
for (size_t i = 0; i < cd.fields.length; i++)
{
VarDeclaration v = cd.fields[i];
Initializer _init = v._init;
if (_init)
{
if (_init.isVoidInitializer())
{
}
else if (ExpInitializer ei = _init.isExpInitializer())
{
// https://issues.dlang.org/show_bug.cgi?id=21319
// This is to prevent re-analyzing the same expression
// over and over again.
if (ei.exp == e)
return false;
Type tb = v.type.toBasetype();
if (implicitMod(ei.exp, tb, mod) == MATCH.nomatch)
return false;
}
else
{
/* Enhancement: handle StructInitializer and ArrayInitializer
*/
return false;
}
}
else if (!v.type.isZeroInit(e.loc))
return false;
}
return cd.baseClass ? convertible(e, cd.baseClass, mod) : true;
}
}
if (!ClassCheck.convertible(e, cd, mod))
return MATCH.nomatch;
}
}
else
{
Expression earg = e.newtype.defaultInitLiteral(e.loc);
Type targ = e.newtype.toBasetype();
if (implicitMod(earg, targ, mod) == MATCH.nomatch)
return MATCH.nomatch;
}
/* Success
*/
return MATCH.constant;
}
MATCH visitSlice(SliceExp e)
{
//printf("SliceExp::implicitConvTo e = %s, type = %s\n", e.toChars(), e.type.toChars());
auto result = visit(e);
if (result != MATCH.nomatch)
return result;
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.ty == Tsarray && typeb.ty == Tarray)
{
typeb = toStaticArrayType(e);
if (typeb)
{
// Try: T[] -> T[dim]
// (Slice with compile-time known boundaries to static array)
result = typeb.implicitConvTo(t);
if (result > MATCH.convert)
result = MATCH.convert; // match with implicit conversion at most
}
return result;
}
/* If the only reason it won't convert is because of the mod bits,
* then test for conversion by seeing if e1 can be converted with those
* same mod bits.
*/
Type t1b = e.e1.type.toBasetype();
if (tb.ty == Tarray && typeb.equivalent(tb))
{
Type tbn = tb.nextOf();
Type tx = null;
/* If e.e1 is dynamic array or pointer, the uniqueness of e.e1
* is equivalent with the uniqueness of the referred data. And in here
* we can have arbitrary typed reference for that.
*/
if (t1b.ty == Tarray)
tx = tbn.arrayOf();
if (t1b.ty == Tpointer)
tx = tbn.pointerTo();
/* If e.e1 is static array, at least it should be an rvalue.
* If not, e.e1 is a reference, and its uniqueness does not link
* to the uniqueness of the referred data.
*/
if (t1b.ty == Tsarray && !e.e1.isLvalue())
tx = tbn.sarrayOf(t1b.size() / tbn.size());
if (tx)
{
result = e.e1.implicitConvTo(tx);
if (result > MATCH.constant) // Match level is MATCH.constant at best.
result = MATCH.constant;
}
}
// Enhancement 10724
if (tb.ty == Tpointer && e.e1.op == EXP.string_)
result = e.e1.implicitConvTo(t);
return result;
}
MATCH visitTuple(TupleExp e)
{
auto result = e.type.implicitConvTo(t);
if (result != MATCH.nomatch)
return result;
/* If target type is a tuple of same length, test conversion of
* each expression to the corresponding type in the tuple.
*/
TypeTuple totuple = t.isTypeTuple();
if (totuple && e.exps.length == totuple.arguments.length)
{
result = MATCH.exact;
foreach (i, ex; *e.exps)
{
auto to = (*totuple.arguments)[i].type;
MATCH mi = ex.implicitConvTo(to);
if (mi < result)
result = mi;
}
}
return result;
}
switch (e.op)
{
default : return visit(e);
case EXP.add : return visitAdd(e.isAddExp());
case EXP.min : return visitMin(e.isMinExp());
case EXP.int64 : return visitInteger(e.isIntegerExp());
case EXP.error : return visitError(e.isErrorExp());
case EXP.null_ : return visitNull(e.isNullExp());
case EXP.structLiteral : return visitStructLiteral(e.isStructLiteralExp());
case EXP.string_ : return visitString(e.isStringExp());
case EXP.arrayLiteral : return visitArrayLiteral(e.isArrayLiteralExp());
case EXP.assocArrayLiteral: return visitAssocArrayLiteral(e.isAssocArrayLiteralExp());
case EXP.call : return visitCall(e.isCallExp());
case EXP.address : return visitAddr(e.isAddrExp());
case EXP.symbolOffset : return visitSymOff(e.isSymOffExp());
case EXP.delegate_ : return visitDelegate(e.isDelegateExp());
case EXP.function_ : return visitFunc(e.isFuncExp());
case EXP.and : return visitAnd(e.isAndExp());
case EXP.or : return visitOr(e.isOrExp());
case EXP.xor : return visitXor(e.isXorExp());
case EXP.question : return visitCond(e.isCondExp());
case EXP.comma : return visitComma(e.isCommaExp());
case EXP.cast_ : return visitCast(e.isCastExp());
case EXP.new_ : return visitNew(e.isNewExp());
case EXP.slice : return visitSlice(e.isSliceExp());
case EXP.tuple : return visitTuple(e.isTupleExp());
}
}
/**
* Same as implicitConvTo(); except follow C11 rules, which are quite a bit
* more permissive than D.
* C11 6.3 and 6.5.16.1
* Params:
* e = Expression that is to be casted
* t = Expected resulting type
* Returns:
* The `MATCH` level between `e.type` and `t`.
*/
MATCH cimplicitConvTo(Expression e, Type t)
{
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.equals(typeb))
return MATCH.exact;
if ((typeb.isintegral() || typeb.isfloating()) &&
(tb.isintegral() || tb.isfloating()))
return MATCH.convert;
if (tb.ty == Tpointer && typeb.isintegral()) // C11 6.3.2.3-5
return MATCH.convert;
if (tb.isintegral() && typeb.ty == Tpointer) // C11 6.3.2.3-6
return MATCH.convert;
if (tb.ty == Tpointer && typeb.ty == Tpointer) // C11 6.3.2.3-7
return MATCH.convert;
return implicitConvTo(e, t);
}
/*****************************************
*/
Type toStaticArrayType(SliceExp e)
{
if (e.lwr && e.upr)
{
// For the following code to work, e should be optimized beforehand.
// (eg. $ in lwr and upr should be already resolved, if possible)
Expression lwr = e.lwr.optimize(WANTvalue);
Expression upr = e.upr.optimize(WANTvalue);
if (lwr.isConst() && upr.isConst())
{
size_t len = cast(size_t)(upr.toUInteger() - lwr.toUInteger());
return e.type.toBasetype().nextOf().sarrayOf(len);
}
}
else
{
Type t1b = e.e1.type.toBasetype();
if (t1b.ty == Tsarray)
return t1b;
}
return null;
}
/**************************************
* Do an explicit cast.
* Assume that the expression `e` does not have any indirections.
* (Parameter 'att' is used to stop 'alias this' recursion)
*/
Expression castTo(Expression e, Scope* sc, Type t, Type att = null)
{
//printf("castTo(e: %s from: %s to: %s\n", e.toChars(), e.type.toChars(), t.toChars());
Expression visit(Expression e)
{
//printf("Expression::castTo(this=%s, t=%s)\n", e.toChars(), t.toChars());
version (none)
{
printf("Expression::castTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
if (e.type.equals(t))
{
return e;
}
if (e.type.isTypeNoreturn())
{
return specialNoreturnCast(e, t);
}
if (auto ve = e.isVarExp())
{
VarDeclaration v = ve.var.isVarDeclaration();
if (v && v.storage_class & STC.manifest)
{
auto result = e.ctfeInterpret();
/* https://issues.dlang.org/show_bug.cgi?id=18236
*
* The expression returned by ctfeInterpret points
* to the line where the manifest constant was declared
* so we need to update the location before trying to cast
*/
result.loc = e.loc;
return result.castTo(sc, t);
}
}
Type tob = t.toBasetype();
Type t1b = e.type.toBasetype();
if (tob.equals(t1b))
{
auto result = e.copy(); // because of COW for assignment to e.type
result.type = t;
return result;
}
/* Make semantic error against invalid cast between concrete types.
* Assume that 'e' is never be any placeholder expressions.
* The result of these checks should be consistent with CastExp::toElem().
*/
// Fat Value types
const(bool) tob_isFV = (tob.ty == Tstruct || tob.ty == Tsarray || tob.ty == Tvector);
const(bool) t1b_isFV = (t1b.ty == Tstruct || t1b.ty == Tsarray || t1b.ty == Tvector);
// Fat Reference types
const(bool) tob_isFR = (tob.ty == Tarray || tob.ty == Tdelegate);
const(bool) t1b_isFR = (t1b.ty == Tarray || t1b.ty == Tdelegate);
// Reference types
const(bool) tob_isR = (tob_isFR || tob.ty == Tpointer || tob.ty == Taarray || tob.ty == Tclass);
const(bool) t1b_isR = (t1b_isFR || t1b.ty == Tpointer || t1b.ty == Taarray || t1b.ty == Tclass);
// Arithmetic types (== valueable basic types)
const(bool) tob_isA = ((tob.isintegral() || tob.isfloating()) && tob.ty != Tvector);
const(bool) t1b_isA = ((t1b.isintegral() || t1b.isfloating()) && t1b.ty != Tvector);
// Try casting the alias this member.
// Return the expression if it succeeds, null otherwise.
Expression tryAliasThisCast()
{
if (isRecursiveAliasThis(att, t1b))
return null;
/* Forward the cast to our alias this member, rewrite to:
* cast(to)e1.aliasthis
*/
auto exp = resolveAliasThis(sc, e);
const errors = global.startGagging();
exp = castTo(exp, sc, t, att);
return global.endGagging(errors) ? null : exp;
}
bool hasAliasThis;
if (AggregateDeclaration t1ad = isAggregate(t1b))
{
AggregateDeclaration toad = isAggregate(tob);
if (t1ad != toad && t1ad.aliasthis)
{
if (t1b.ty == Tclass && tob.ty == Tclass)
{
ClassDeclaration t1cd = t1b.isClassHandle();
ClassDeclaration tocd = tob.isClassHandle();
int offset;
if (tocd.isBaseOf(t1cd, &offset))
goto Lok;
}
hasAliasThis = true;
}
}
else if (tob.ty == Tvector && t1b.ty != Tvector)
{
//printf("test1 e = %s, e.type = %s, tob = %s\n", e.toChars(), e.type.toChars(), tob.toChars());
TypeVector tv = tob.isTypeVector();
Expression result = new CastExp(e.loc, e, tv.elementType());
result = new VectorExp(e.loc, result, tob);
result = result.expressionSemantic(sc);
return result;
}
else if (tob.ty != Tvector && t1b.ty == Tvector)
{
// T[n] <-- __vector(U[m])
if (tob.ty == Tsarray)
{
if (t1b.size(e.loc) == tob.size(e.loc))
goto Lok;
}
goto Lfail;
}
else if (t1b.implicitConvTo(tob) == MATCH.constant && t.equals(e.type.constOf()))
{
auto result = e.copy();
result.type = t;
return result;
}
// arithmetic values vs. other arithmetic values
// arithmetic values vs. T*
if (tob_isA && (t1b_isA || t1b.ty == Tpointer) || t1b_isA && (tob_isA || tob.ty == Tpointer))
{
goto Lok;
}
// arithmetic values vs. references or fat values
if (tob_isA && (t1b_isR || t1b_isFV) || t1b_isA && (tob_isR || tob_isFV))
{
goto Lfail;
}
// Bugzlla 3133: A cast between fat values is possible only when the sizes match.
if (tob_isFV && t1b_isFV)
{
if (hasAliasThis)
{
auto result = tryAliasThisCast();
if (result)
return result;
}
if (t1b.size(e.loc) == tob.size(e.loc))
goto Lok;
auto ts = toAutoQualChars(e.type, t);
e.error("cannot cast expression `%s` of type `%s` to `%s` because of different sizes",
e.toChars(), ts[0], ts[1]);
return ErrorExp.get();
}
// Fat values vs. null or references
if (tob_isFV && (t1b.ty == Tnull || t1b_isR) || t1b_isFV && (tob.ty == Tnull || tob_isR))
{
if (tob.ty == Tpointer && t1b.ty == Tsarray)
{
// T[n] sa;
// cast(U*)sa; // ==> cast(U*)sa.ptr;
return new AddrExp(e.loc, e, t);
}
if (tob.ty == Tarray && t1b.ty == Tsarray)
{
// T[n] sa;
// cast(U[])sa; // ==> cast(U[])sa[];
const fsize = t1b.nextOf().size();
const tsize = tob.nextOf().size();
if (fsize == SIZE_INVALID || tsize == SIZE_INVALID)
{
return ErrorExp.get();
}
if (fsize != tsize)
{
const dim = t1b.isTypeSArray().dim.toInteger();
if (tsize == 0 || (dim * fsize) % tsize != 0)
{
e.error("cannot cast expression `%s` of type `%s` to `%s` since sizes don't line up",
e.toChars(), e.type.toChars(), t.toChars());
return ErrorExp.get();
}
}
goto Lok;
}
goto Lfail;
}
/* For references, any reinterpret casts are allowed to same 'ty' type.
* T* to U*
* R1 function(P1) to R2 function(P2)
* R1 delegate(P1) to R2 delegate(P2)
* T[] to U[]
* V1[K1] to V2[K2]
* class/interface A to B (will be a dynamic cast if possible)
*/
if (tob.ty == t1b.ty && tob_isR && t1b_isR)
goto Lok;
// typeof(null) <-- non-null references or values
if (tob.ty == Tnull && t1b.ty != Tnull)
goto Lfail; // https://issues.dlang.org/show_bug.cgi?id=14629
// typeof(null) --> non-null references or arithmetic values
if (t1b.ty == Tnull && tob.ty != Tnull)
goto Lok;
// Check size mismatch of references.
// Tarray and Tdelegate are (void*).sizeof*2, but others have (void*).sizeof.
if (tob_isFR && t1b_isR || t1b_isFR && tob_isR)
{
if (tob.ty == Tpointer && t1b.ty == Tarray)
{
// T[] da;
// cast(U*)da; // ==> cast(U*)da.ptr;
goto Lok;
}
if (tob.ty == Tpointer && t1b.ty == Tdelegate)
{
// void delegate() dg;
// cast(U*)dg; // ==> cast(U*)dg.ptr;
// Note that it happens even when U is a Tfunction!
e.deprecation("casting from %s to %s is deprecated", e.type.toChars(), t.toChars());
goto Lok;
}
goto Lfail;
}
if (t1b.ty == Tvoid && tob.ty != Tvoid)
{
Lfail:
/* if the cast cannot be performed, maybe there is an alias
* this that can be used for casting.
*/
if (hasAliasThis)
{
auto result = tryAliasThisCast();
if (result)
return result;
}
e.error("cannot cast expression `%s` of type `%s` to `%s`", e.toChars(), e.type.toChars(), t.toChars());
return ErrorExp.get();
}
Lok:
auto result = new CastExp(e.loc, e, t);
result.type = t; // Don't call semantic()
//printf("Returning: %s\n", result.toChars());
return result;
}
Expression visitError(ErrorExp e)
{
return e;
}
Expression visitReal(RealExp e)
{
if (!e.type.equals(t))
{
if ((e.type.isreal() && t.isreal()) || (e.type.isimaginary() && t.isimaginary()))
{
auto result = e.copy();
result.type = t;
return result;
}
else
return visit(e);
}
return e;
}
Expression visitComplex(ComplexExp e)
{
if (!e.type.equals(t))
{
if (e.type.iscomplex() && t.iscomplex())
{
auto result = e.copy();
result.type = t;
return result;
}
else
return visit(e);
}
return e;
}
Expression visitStructLiteral(StructLiteralExp e)
{
auto result = visit(e);
if (auto sle = result.isStructLiteralExp())
sle.stype = t; // commit type
return result;
}
Expression visitString(StringExp e)
{
/* This follows copy-on-write; any changes to 'this'
* will result in a copy.
* The this.string member is considered immutable.
*/
int copied = 0;
//printf("StringExp::castTo(t = %s), '%s' committed = %d\n", t.toChars(), e.toChars(), e.committed);
if (!e.committed && t.ty == Tpointer && t.nextOf().ty == Tvoid &&
(!sc || !(sc.flags & SCOPE.Cfile)))
{
e.error("cannot convert string literal to `void*`");
return ErrorExp.get();
}
StringExp se = e;
Expression lcast()
{
auto result = new CastExp(e.loc, se, t);
result.type = t; // so semantic() won't be run on e
return result;
}
if (!e.committed)
{
se = e.copy().isStringExp();
se.committed = 1;
copied = 1;
}
if (e.type.equals(t))
{
return se;
}
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
//printf("\ttype = %s\n", e.type.toChars());
if (tb.ty == Tdelegate && typeb.ty != Tdelegate)
{
return visit(e);
}
if (typeb.equals(tb))
{
if (!copied)
{
se = e.copy().isStringExp();
copied = 1;
}
se.type = t;
return se;
}
/* Handle reinterpret casts:
* cast(wchar[3])"abcd"c --> [\u6261, \u6463, \u0000]
* cast(wchar[2])"abcd"c --> [\u6261, \u6463]
* cast(wchar[1])"abcd"c --> [\u6261]
* cast(char[4])"a" --> ['a', 0, 0, 0]
*/
if (e.committed && tb.ty == Tsarray && typeb.ty == Tarray)
{
se = e.copy().isStringExp();
uinteger_t szx = tb.nextOf().size();
assert(szx <= 255);
se.sz = cast(ubyte)szx;
se.len = cast(size_t)tb.isTypeSArray().dim.toInteger();
se.committed = 1;
se.type = t;
/* If larger than source, pad with zeros.
*/
const fullSize = (se.len + 1) * se.sz; // incl. terminating 0
if (fullSize > (e.len + 1) * e.sz)
{
void* s = mem.xmalloc(fullSize);
const srcSize = e.len * e.sz;
const data = se.peekData();
memcpy(s, data.ptr, srcSize);
memset(s + srcSize, 0, fullSize - srcSize);
se.setData(s, se.len, se.sz);
}
return se;
}
if (tb.ty != Tsarray && tb.ty != Tarray && tb.ty != Tpointer)
{
if (!copied)
{
se = e.copy().isStringExp();
copied = 1;
}
return lcast();
}
if (typeb.ty != Tsarray && typeb.ty != Tarray && typeb.ty != Tpointer)
{
if (!copied)
{
se = e.copy().isStringExp();
copied = 1;
}
return lcast();
}
const nextSz = typeb.nextOf().size();
if (nextSz == SIZE_INVALID)
{
return ErrorExp.get();
}
if (nextSz == tb.nextOf().size())
{
if (!copied)
{
se = e.copy().isStringExp();
copied = 1;
}
if (tb.ty == Tsarray)
goto L2; // handle possible change in static array dimension
se.type = t;
return se;
}
if (e.committed)
goto Lcast;
auto X(T, U)(T tf, U tt)
{
return (cast(int)tf * 256 + cast(int)tt);
}
{
OutBuffer buffer;
size_t newlen = 0;
int tfty = typeb.nextOf().toBasetype().ty;
int ttty = tb.nextOf().toBasetype().ty;
switch (X(tfty, ttty))
{
case X(Tchar, Tchar):
case X(Twchar, Twchar):
case X(Tdchar, Tdchar):
break;
case X(Tchar, Twchar):
for (size_t u = 0; u < e.len;)
{
dchar c;
if (const s = utf_decodeChar(se.peekString(), u, c))
e.error("%.*s", cast(int)s.length, s.ptr);
else
buffer.writeUTF16(c);
}
newlen = buffer.length / 2;
buffer.writeUTF16(0);
goto L1;
case X(Tchar, Tdchar):
for (size_t u = 0; u < e.len;)
{
dchar c;
if (const s = utf_decodeChar(se.peekString(), u, c))
e.error("%.*s", cast(int)s.length, s.ptr);
buffer.write4(c);
newlen++;
}
buffer.write4(0);
goto L1;
case X(Twchar, Tchar):
for (size_t u = 0; u < e.len;)
{
dchar c;
if (const s = utf_decodeWchar(se.peekWstring(), u, c))
e.error("%.*s", cast(int)s.length, s.ptr);
else
buffer.writeUTF8(c);
}
newlen = buffer.length;
buffer.writeUTF8(0);
goto L1;
case X(Twchar, Tdchar):
for (size_t u = 0; u < e.len;)
{
dchar c;
if (const s = utf_decodeWchar(se.peekWstring(), u, c))
e.error("%.*s", cast(int)s.length, s.ptr);
buffer.write4(c);
newlen++;
}
buffer.write4(0);
goto L1;
case X(Tdchar, Tchar):
for (size_t u = 0; u < e.len; u++)
{
uint c = se.peekDstring()[u];
if (!utf_isValidDchar(c))
e.error("invalid UCS-32 char \\U%08x", c);
else
buffer.writeUTF8(c);
newlen++;
}
newlen = buffer.length;
buffer.writeUTF8(0);
goto L1;
case X(Tdchar, Twchar):
for (size_t u = 0; u < e.len; u++)
{
uint c = se.peekDstring()[u];
if (!utf_isValidDchar(c))
e.error("invalid UCS-32 char \\U%08x", c);
else
buffer.writeUTF16(c);
newlen++;
}
newlen = buffer.length / 2;
buffer.writeUTF16(0);
goto L1;
L1:
if (!copied)
{
se = e.copy().isStringExp();
copied = 1;
}
{
uinteger_t szx = tb.nextOf().size();
assert(szx <= 255);
se.setData(buffer.extractSlice().ptr, newlen, cast(ubyte)szx);
}
break;
default:
assert(typeb.nextOf().size() != tb.nextOf().size());
goto Lcast;
}
}
L2:
assert(copied);
// See if need to truncate or extend the literal
if (auto tsa = tb.isTypeSArray())
{
size_t dim2 = cast(size_t)tsa.dim.toInteger();
//printf("dim from = %d, to = %d\n", cast(int)se.len, cast(int)dim2);
// Changing dimensions
if (dim2 != se.len)
{
// Copy when changing the string literal
const newsz = se.sz;
const d = (dim2 < se.len) ? dim2 : se.len;
void* s = mem.xmalloc((dim2 + 1) * newsz);
memcpy(s, se.peekData().ptr, d * newsz);
// Extend with 0, add terminating 0
memset(s + d * newsz, 0, (dim2 + 1 - d) * newsz);
se.setData(s, dim2, newsz);
}
}
se.type = t;
return se;
Lcast:
auto result = new CastExp(e.loc, se, t);
result.type = t; // so semantic() won't be run on e
return result;
}
Expression visitAddr(AddrExp e)
{
version (none)
{
printf("AddrExp::castTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.equals(typeb))
{
auto result = e.copy();
result.type = t;
return result;
}
// Look for pointers to functions where the functions are overloaded.
if (e.e1.isOverExp() &&
(tb.ty == Tpointer || tb.ty == Tdelegate) && tb.nextOf().ty == Tfunction)
{
OverExp eo = e.e1.isOverExp();
FuncDeclaration f = null;
for (size_t i = 0; i < eo.vars.a.length; i++)
{
auto s = eo.vars.a[i];
auto f2 = s.isFuncDeclaration();
assert(f2);
if (f2.overloadExactMatch(tb.nextOf()))
{
if (f)
{
/* Error if match in more than one overload set,
* even if one is a 'better' match than the other.
*/
ScopeDsymbol.multiplyDefined(e.loc, f, f2);
}
else
f = f2;
}
}
if (f)
{
f.tookAddressOf++;
auto se = new SymOffExp(e.loc, f, 0, false);
auto se2 = se.expressionSemantic(sc);
// Let SymOffExp::castTo() do the heavy lifting
return visit(se2);
}
}
if (e.e1.isVarExp() &&
typeb.ty == Tpointer && typeb.nextOf().ty == Tfunction &&
tb.ty == Tpointer && tb.nextOf().ty == Tfunction)
{
auto ve = e.e1.isVarExp();
auto f = ve.var.isFuncDeclaration();
if (f)
{
assert(f.isImportedSymbol());
f = f.overloadExactMatch(tb.nextOf());
if (f)
{
Expression result = new VarExp(e.loc, f, false);
result.type = f.type;
result = new AddrExp(e.loc, result, t);
return result;
}
}
}
if (auto f = isFuncAddress(e))
{
if (f.checkForwardRef(e.loc))
{
return ErrorExp.get();
}
}
return visit(e);
}
Expression visitTuple(TupleExp e)
{
if (e.type.equals(t))
{
return e;
}
/* If target type is a tuple of same length, cast each expression to
* the corresponding type in the tuple.
*/
TypeTuple totuple;
if (auto tt = t.isTypeTuple())
totuple = e.exps.length == tt.arguments.length ? tt : null;
TupleExp te = e.copy().isTupleExp();
te.e0 = e.e0 ? e.e0.copy() : null;
te.exps = e.exps.copy();
for (size_t i = 0; i < te.exps.length; i++)
{
Expression ex = (*te.exps)[i];
ex = ex.castTo(sc, totuple ? (*totuple.arguments)[i].type : t);
(*te.exps)[i] = ex;
}
if (totuple)
te.type = totuple;
return te;
/* Questionable behavior: In here, result.type is not set to t
* if target type is not a tuple of same length.
* Therefoe:
* TypeTuple!(int, int) values;
* auto values2 = cast(long)values;
* // typeof(values2) == TypeTuple!(int, int) !!
*
* Only when the casted tuple is immediately expanded, it would work.
* auto arr = [cast(long)values];
* // typeof(arr) == long[]
*/
}
Expression visitArrayLiteral(ArrayLiteralExp e)
{
version (none)
{
printf("ArrayLiteralExp::castTo(this=%s, type=%s, => %s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
ArrayLiteralExp ae = e;
Type tb = t.toBasetype();
if (tb.ty == Tarray)
{
if (checkArrayLiteralEscape(sc, ae, false))
{
return ErrorExp.get();
}
}
if (e.type == t)
{
return e;
}
Type typeb = e.type.toBasetype();
if ((tb.ty == Tarray || tb.ty == Tsarray) &&
(typeb.ty == Tarray || typeb.ty == Tsarray))
{
if (tb.nextOf().toBasetype().ty == Tvoid && typeb.nextOf().toBasetype().ty != Tvoid)
{
// Don't do anything to cast non-void[] to void[]
}
else if (typeb.ty == Tsarray && typeb.nextOf().toBasetype().ty == Tvoid)
{
// Don't do anything for casting void[n] to others
}
else
{
if (auto tsa = tb.isTypeSArray())
{
if (e.elements.length != tsa.dim.toInteger())
goto L1;
}
ae = e.copy().isArrayLiteralExp();
if (e.basis)
ae.basis = e.basis.castTo(sc, tb.nextOf());
ae.elements = e.elements.copy();
for (size_t i = 0; i < e.elements.length; i++)
{
Expression ex = (*e.elements)[i];
if (!ex)
continue;
ex = ex.castTo(sc, tb.nextOf());
(*ae.elements)[i] = ex;
}
ae.type = t;
return ae;
}
}
else if (tb.ty == Tpointer && typeb.ty == Tsarray)
{
Type tp = typeb.nextOf().pointerTo();
if (!tp.equals(ae.type))
{
ae = e.copy().isArrayLiteralExp();
ae.type = tp;
}
}
else if (tb.ty == Tvector && (typeb.ty == Tarray || typeb.ty == Tsarray))
{
// Convert array literal to vector type
TypeVector tv = tb.isTypeVector();
TypeSArray tbase = tv.basetype.isTypeSArray();
assert(tbase.ty == Tsarray);
const edim = e.elements.length;
const tbasedim = tbase.dim.toInteger();
if (edim > tbasedim)
goto L1;
ae = e.copy().isArrayLiteralExp();
ae.type = tbase; // https://issues.dlang.org/show_bug.cgi?id=12642
ae.elements = e.elements.copy();
Type telement = tv.elementType();
foreach (i; 0 .. edim)
{
Expression ex = (*e.elements)[i];
ex = ex.castTo(sc, telement);
(*ae.elements)[i] = ex;
}
// Fill in the rest with the default initializer
ae.elements.setDim(cast(size_t)tbasedim);
foreach (i; edim .. cast(size_t)tbasedim)
{
Expression ex = typeb.nextOf.defaultInitLiteral(e.loc);
ex = ex.castTo(sc, telement);
(*ae.elements)[i] = ex;
}
Expression ev = new VectorExp(e.loc, ae, tb);
ev = ev.expressionSemantic(sc);
return ev;
}
L1:
return visit(ae);
}
Expression visitAssocArrayLiteral(AssocArrayLiteralExp e)
{
//printf("AssocArrayLiteralExp::castTo(this=%s, type=%s, => %s)\n", e.toChars(), e.type.toChars(), t.toChars());
if (e.type == t)
{
return e;
}
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.ty == Taarray && typeb.ty == Taarray &&
tb.nextOf().toBasetype().ty != Tvoid)
{
AssocArrayLiteralExp ae = e.copy().isAssocArrayLiteralExp();
ae.keys = e.keys.copy();
ae.values = e.values.copy();
assert(e.keys.length == e.values.length);
for (size_t i = 0; i < e.keys.length; i++)
{
Expression ex = (*e.values)[i];
ex = ex.castTo(sc, tb.nextOf());
(*ae.values)[i] = ex;
ex = (*e.keys)[i];
ex = ex.castTo(sc, tb.isTypeAArray().index);
(*ae.keys)[i] = ex;
}
ae.type = t;
return ae;
}
return visit(e);
}
Expression visitSymOff(SymOffExp e)
{
version (none)
{
printf("SymOffExp::castTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
if (e.type == t && !e.hasOverloads)
{
return e;
}
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.equals(typeb))
{
auto result = e.copy();
result.type = t;
result.isSymOffExp().hasOverloads = false;
return result;
}
// Look for pointers to functions where the functions are overloaded.
if (e.hasOverloads &&
typeb.ty == Tpointer && typeb.nextOf().ty == Tfunction &&
(tb.ty == Tpointer || tb.ty == Tdelegate) && tb.nextOf().ty == Tfunction)
{
FuncDeclaration f = e.var.isFuncDeclaration();
f = f ? f.overloadExactMatch(tb.nextOf()) : null;
if (f)
{
Expression result;
if (tb.ty == Tdelegate)
{
if (f.needThis() && hasThis(sc))
{
result = new DelegateExp(e.loc, new ThisExp(e.loc), f, false);
result = result.expressionSemantic(sc);
}
else if (f.needThis())
{
e.error("no `this` to create delegate for `%s`", f.toChars());
return ErrorExp.get();
}
else if (f.isNested())
{
result = new DelegateExp(e.loc, IntegerExp.literal!0, f, false);
result = result.expressionSemantic(sc);
}
else
{
e.error("cannot cast from function pointer to delegate");
return ErrorExp.get();
}
}
else
{
result = new SymOffExp(e.loc, f, 0, false);
result.type = t;
}
f.tookAddressOf++;
return result;
}
}
if (auto f = isFuncAddress(e))
{
if (f.checkForwardRef(e.loc))
{
return ErrorExp.get();
}
}
return visit(e);
}
Expression visitDelegate(DelegateExp e)
{
version (none)
{
printf("DelegateExp::castTo(this=%s, type=%s, t=%s)\n", e.toChars(), e.type.toChars(), t.toChars());
}
static immutable msg = "cannot form delegate due to covariant return type";
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (tb.equals(typeb) && !e.hasOverloads)
{
int offset;
e.func.tookAddressOf++;
if (e.func.tintro && e.func.tintro.nextOf().isBaseOf(e.func.type.nextOf(), &offset) && offset)
e.error("%s", msg.ptr);
auto result = e.copy();
result.type = t;
return result;
}
// Look for delegates to functions where the functions are overloaded.
if (typeb.ty == Tdelegate && tb.ty == Tdelegate)
{
if (e.func)
{
auto f = e.func.overloadExactMatch(tb.nextOf());
if (f)
{
int offset;
if (f.tintro && f.tintro.nextOf().isBaseOf(f.type.nextOf(), &offset) && offset)
e.error("%s", msg.ptr);
if (f != e.func) // if address not already marked as taken
f.tookAddressOf++;
auto result = new DelegateExp(e.loc, e.e1, f, false, e.vthis2);
result.type = t;
return result;
}
if (e.func.tintro)
e.error("%s", msg.ptr);
}
}
if (auto f = isFuncAddress(e))
{
if (f.checkForwardRef(e.loc))
{
return ErrorExp.get();
}
}
return visit(e);
}
Expression visitFunc(FuncExp e)
{
//printf("FuncExp::castTo type = %s, t = %s\n", e.type.toChars(), t.toChars());
FuncExp fe;
if (e.matchType(t, sc, &fe, 1) > MATCH.nomatch)
{
return fe;
}
return visit(e);
}
Expression visitCond(CondExp e)
{
if (!e.type.equals(t))
{
auto result = new CondExp(e.loc, e.econd, e.e1.castTo(sc, t), e.e2.castTo(sc, t));
result.type = t;
return result;
}
return e;
}
Expression visitComma(CommaExp e)
{
Expression e2c = e.e2.castTo(sc, t);
if (e2c != e.e2)
{
auto result = new CommaExp(e.loc, e.e1, e2c);
result.type = e2c.type;
return result;
}
else
{
e.type = e.e2.type;
return e;
}
}
Expression visitSlice(SliceExp e)
{
//printf("SliceExp::castTo e = %s, type = %s, t = %s\n", e.toChars(), e.type.toChars(), t.toChars());
Type tb = t.toBasetype();
Type typeb = e.type.toBasetype();
if (e.type.equals(t) || typeb.ty != Tarray ||
(tb.ty != Tarray && tb.ty != Tsarray))
{
return visit(e);
}
if (tb.ty == Tarray)
{
if (typeb.nextOf().equivalent(tb.nextOf()))
{
// T[] to const(T)[]
auto result = e.copy();
result.type = t;
return result;
}
else
{
return visit(e);
}
}
// Handle the cast from Tarray to Tsarray with CT-known slicing
TypeSArray tsa;
{
Type t = toStaticArrayType(e);
tsa = t ? t.isTypeSArray() : null;
}
if (tsa && tsa.size(e.loc) == tb.size(e.loc))
{
/* Match if the sarray sizes are equal:
* T[a .. b] to const(T)[b-a]
* T[a .. b] to U[dim] if (T.sizeof*(b-a) == U.sizeof*dim)
*
* If a SliceExp has Tsarray, it will become lvalue.
* That's handled in SliceExp::isLvalue and toLvalue
*/
auto result = e.copy();
result.type = t;
return result;
}
if (tsa && tsa.dim.equals(tb.isTypeSArray().dim))
{
/* Match if the dimensions are equal
* with the implicit conversion of e.e1:
* cast(float[2]) [2.0, 1.0, 0.0][0..2];
*/
Type t1b = e.e1.type.toBasetype();
if (t1b.ty == Tsarray)
t1b = tb.nextOf().sarrayOf(t1b.isTypeSArray().dim.toInteger());
else if (t1b.ty == Tarray)
t1b = tb.nextOf().arrayOf();
else if (t1b.ty == Tpointer)
t1b = tb.nextOf().pointerTo();
else
assert(0);
if (e.e1.implicitConvTo(t1b) > MATCH.nomatch)
{
Expression e1x = e.e1.implicitCastTo(sc, t1b);
assert(e1x.op != EXP.error);
e = e.copy().isSliceExp();
e.e1 = e1x;
e.type = t;
return e;
}
}
auto ts = toAutoQualChars(tsa ? tsa : e.type, t);
e.error("cannot cast expression `%s` of type `%s` to `%s`",
e.toChars(), ts[0], ts[1]);
return ErrorExp.get();
}
// Casting to noreturn isn't an actual cast
// Rewrite cast(<qual> noreturn) <exp>
// as <exp>, assert(false)
if (t.isTypeNoreturn())
{
// Don't generate an unreachable assert(false) if e will abort
if (e.type.isTypeNoreturn())
{
// Paint e to accomodate for different type qualifiers
e.type = t;
return e;
}
auto ini = t.defaultInitLiteral(e.loc);
return Expression.combine(e, ini);
}
switch (e.op)
{
default : return visit(e);
case EXP.error : return visitError(e.isErrorExp());
case EXP.float64 : return visitReal(e.isRealExp());
case EXP.complex80 : return visitComplex(e.isComplexExp());
case EXP.structLiteral : return visitStructLiteral(e.isStructLiteralExp());
case EXP.string_ : return visitString(e.isStringExp());
case EXP.address : return visitAddr(e.isAddrExp());
case EXP.tuple : return visitTuple(e.isTupleExp());
case EXP.arrayLiteral : return visitArrayLiteral(e.isArrayLiteralExp());
case EXP.assocArrayLiteral: return visitAssocArrayLiteral(e.isAssocArrayLiteralExp());
case EXP.symbolOffset : return visitSymOff(e.isSymOffExp());
case EXP.delegate_ : return visitDelegate(e.isDelegateExp());
case EXP.function_ : return visitFunc(e.isFuncExp());
case EXP.question : return visitCond(e.isCondExp());
case EXP.comma : return visitComma(e.isCommaExp());
case EXP.slice : return visitSlice(e.isSliceExp());
}
}
/****************************************
* Set type inference target
* t Target type
* flag 1: don't put an error when inference fails
*/
Expression inferType(Expression e, Type t, int flag = 0)
{
Expression visitAle(ArrayLiteralExp ale)
{
Type tb = t.toBasetype();
if (tb.ty == Tarray || tb.ty == Tsarray)
{
Type tn = tb.nextOf();
if (ale.basis)
ale.basis = inferType(ale.basis, tn, flag);
for (size_t i = 0; i < ale.elements.length; i++)
{
if (Expression e = (*ale.elements)[i])
{
e = inferType(e, tn, flag);
(*ale.elements)[i] = e;
}
}
}
return ale;
}
Expression visitAar(AssocArrayLiteralExp aale)
{
Type tb = t.toBasetype();
if (auto taa = tb.isTypeAArray())
{
Type ti = taa.index;
Type tv = taa.nextOf();
for (size_t i = 0; i < aale.keys.length; i++)
{
if (Expression e = (*aale.keys)[i])
{
e = inferType(e, ti, flag);
(*aale.keys)[i] = e;
}
}
for (size_t i = 0; i < aale.values.length; i++)
{
if (Expression e = (*aale.values)[i])
{
e = inferType(e, tv, flag);
(*aale.values)[i] = e;
}
}
}
return aale;
}
Expression visitFun(FuncExp fe)
{
//printf("FuncExp::inferType('%s'), to=%s\n", fe.type ? fe.type.toChars() : "null", t.toChars());
if (t.ty == Tdelegate || t.ty == Tpointer && t.nextOf().ty == Tfunction)
{
fe.fd.treq = t;
}
return fe;
}
Expression visitTer(CondExp ce)
{
Type tb = t.toBasetype();
ce.e1 = inferType(ce.e1, tb, flag);
ce.e2 = inferType(ce.e2, tb, flag);
return ce;
}
if (t) switch (e.op)
{
case EXP.arrayLiteral: return visitAle(e.isArrayLiteralExp());
case EXP.assocArrayLiteral: return visitAar(e.isAssocArrayLiteralExp());
case EXP.function_: return visitFun(e.isFuncExp());
case EXP.question: return visitTer(e.isCondExp());
default:
}
return e;
}
/****************************************
* Scale addition/subtraction to/from pointer.
*/
Expression scaleFactor(BinExp be, Scope* sc)
{
Type t1b = be.e1.type.toBasetype();
Type t2b = be.e2.type.toBasetype();
Expression eoff;
if (t1b.ty == Tpointer && t2b.isintegral())
{
// Need to adjust operator by the stride
// Replace (ptr + int) with (ptr + (int * stride))
Type t = Type.tptrdiff_t;
uinteger_t stride = t1b.nextOf().size(be.loc);
if (!t.equals(t2b))
be.e2 = be.e2.castTo(sc, t);
eoff = be.e2;
be.e2 = new MulExp(be.loc, be.e2, new IntegerExp(Loc.initial, stride, t));
be.e2.type = t;
be.type = be.e1.type;
}
else if (t2b.ty == Tpointer && t1b.isintegral())
{
// Need to adjust operator by the stride
// Replace (int + ptr) with (ptr + (int * stride))
Type t = Type.tptrdiff_t;
Expression e;
uinteger_t stride = t2b.nextOf().size(be.loc);
if (!t.equals(t1b))
e = be.e1.castTo(sc, t);
else
e = be.e1;
eoff = e;
e = new MulExp(be.loc, e, new IntegerExp(Loc.initial, stride, t));
e.type = t;
be.type = be.e2.type;
be.e1 = be.e2;
be.e2 = e;
}
else
assert(0);
eoff = eoff.optimize(WANTvalue);
if (eoff.op == EXP.int64 && eoff.toInteger() == 0)
{
}
else if (sc.setUnsafe(false, be.loc, "pointer arithmetic not allowed in @safe functions"))
{
return ErrorExp.get();
}
return be;
}
/**************************************
* Return true if e is an empty array literal with dimensionality
* equal to or less than type of other array.
* [], [[]], [[[]]], etc.
* I.e., make sure that [1,2] is compatible with [],
* [[1,2]] is compatible with [[]], etc.
*/
private bool isVoidArrayLiteral(Expression e, Type other)
{
while (e.op == EXP.arrayLiteral && e.type.ty == Tarray && (e.isArrayLiteralExp().elements.length == 1))
{
auto ale = e.isArrayLiteralExp();
e = ale[0];
if (other.ty == Tsarray || other.ty == Tarray)
other = other.nextOf();
else
return false;
}
if (other.ty != Tsarray && other.ty != Tarray)
return false;
Type t = e.type;
return (e.op == EXP.arrayLiteral && t.ty == Tarray && t.nextOf().ty == Tvoid && e.isArrayLiteralExp().elements.length == 0);
}
/**
* Merge types of `e1` and `e2` into a common subset
*
* Parameters `e1` and `e2` will be rewritten in place as needed.
*
* Params:
* sc = Current scope
* op = Operator such as `e1 op e2`. In practice, either EXP.question
* or one of the binary operator.
* pe1 = The LHS of the operation, will be rewritten
* pe2 = The RHS of the operation, will be rewritten
*
* Returns:
* The resulting type in case of success, `null` in case of error
*/
Type typeMerge(Scope* sc, EXP op, ref Expression pe1, ref Expression pe2)
{
//printf("typeMerge() %s op %s\n", e1.toChars(), e2.toChars());
Expression e1 = pe1;
Expression e2 = pe2;
// ImportC: do array/function conversions
if (sc)
{
e1 = e1.arrayFuncConv(sc);
e2 = e2.arrayFuncConv(sc);
}
Type Lret(Type result)
{
pe1 = e1;
pe2 = e2;
version (none)
{
printf("-typeMerge() %s op %s\n", e1.toChars(), e2.toChars());
if (e1.type)
printf("\tt1 = %s\n", e1.type.toChars());
if (e2.type)
printf("\tt2 = %s\n", e2.type.toChars());
printf("\ttype = %s\n", result.toChars());
}
return result;
}
/// Converts one of the expression to the other
Type convert(ref Expression from, Type to)
{
from = from.castTo(sc, to);
return Lret(to);
}
/// Converts both expression to a third type
Type coerce(Type towards)
{
e1 = e1.castTo(sc, towards);
e2 = e2.castTo(sc, towards);
return Lret(towards);
}
Type t1b = e1.type.toBasetype();
Type t2b = e2.type.toBasetype();
if (sc && sc.flags & SCOPE.Cfile)
{
// Integral types can be implicitly converted to pointers
if ((t1b.ty == Tpointer) != (t2b.ty == Tpointer))
{
if (t1b.isintegral())
{
return convert(e1, t2b);
}
else if (t2b.isintegral())
{
return convert(e2, t1b);
}
}
}
if (op != EXP.question || t1b.ty != t2b.ty && (t1b.isTypeBasic() && t2b.isTypeBasic()))
{
if (op == EXP.question && t1b.ty.isSomeChar() && t2b.ty.isSomeChar())
{
e1 = e1.castTo(sc, Type.tdchar);
e2 = e2.castTo(sc, Type.tdchar);
}
else
{
e1 = integralPromotions(e1, sc);
e2 = integralPromotions(e2, sc);
}
}
MATCH m;
Type t1 = e1.type;
Type t2 = e2.type;
assert(t1);
Type t = t1;
/* The start type of alias this type recursion.
* In following case, we should save A, and stop recursion
* if it appears again.
* X -> Y -> [A] -> B -> A -> B -> ...
*/
Type att1 = null;
Type att2 = null;
if (t1.mod != t2.mod &&
t1.ty == Tenum && t2.ty == Tenum &&
t1.isTypeEnum().sym == t2.isTypeEnum().sym)
{
ubyte mod = MODmerge(t1.mod, t2.mod);
t1 = t1.castMod(mod);
t2 = t2.castMod(mod);
}
Lagain:
t1b = t1.toBasetype();
t2b = t2.toBasetype();
const ty = implicitConvCommonTy(t1b.ty, t2b.ty);
if (ty != Terror)
{
const ty1 = implicitConvTy1(t1b.ty, t2b.ty);
const ty2 = implicitConvTy1(t2b.ty, t1b.ty);
if (t1b.ty == ty1) // if no promotions
{
if (t1.equals(t2))
return Lret(t1);
if (t1b.equals(t2b))
return Lret(t1b);
}
t1 = Type.basic[ty1];
t2 = Type.basic[ty2];
if (!(t1 && t2))
return null;
e1 = e1.castTo(sc, t1);
e2 = e2.castTo(sc, t2);
return Lret(Type.basic[ty]);
}
t1 = t1b;
t2 = t2b;
if (t1.ty == Ttuple || t2.ty == Ttuple)
return null;
if (t1.equals(t2))
{
// merging can not result in new enum type
if (t.ty == Tenum)
return Lret(t1b);
return Lret(t);
}
if ((t1.ty == Tpointer && t2.ty == Tpointer) || (t1.ty == Tdelegate && t2.ty == Tdelegate))
{
// Bring pointers to compatible type
Type t1n = t1.nextOf();
Type t2n = t2.nextOf();
if (t1n.equals(t2n))
return Lret(t);
if (t1n.ty == Tvoid) // pointers to void are always compatible
return Lret(t1);
if (t2n.ty == Tvoid)
return Lret(t2);
if (t1.implicitConvTo(t2))
return convert(e1, t2);
if (t2.implicitConvTo(t1))
return convert(e2, t1);
if (t1n.ty == Tfunction && t2n.ty == Tfunction)
{
TypeFunction tf1 = t1n.isTypeFunction();
TypeFunction tf2 = t2n.isTypeFunction();
tf1.purityLevel();
tf2.purityLevel();
TypeFunction d = tf1.syntaxCopy();
if (tf1.purity != tf2.purity)
d.purity = PURE.impure;
assert(d.purity != PURE.fwdref);
d.isnothrow = (tf1.isnothrow && tf2.isnothrow);
d.isnogc = (tf1.isnogc && tf2.isnogc);
if (tf1.trust == tf2.trust)
d.trust = tf1.trust;
else if (tf1.trust <= TRUST.system || tf2.trust <= TRUST.system)
d.trust = TRUST.system;
else
d.trust = TRUST.trusted;
Type tx = (t1.ty == Tdelegate) ? new TypeDelegate(d) : d.pointerTo();
tx = tx.typeSemantic(e1.loc, sc);
if (t1.implicitConvTo(tx) && t2.implicitConvTo(tx))
return coerce(tx);
return null;
}
if (t1n.mod != t2n.mod)
{
if (!t1n.isImmutable() && !t2n.isImmutable() && t1n.isShared() != t2n.isShared())
return null;
ubyte mod = MODmerge(t1n.mod, t2n.mod);
t1 = t1n.castMod(mod).pointerTo();
t2 = t2n.castMod(mod).pointerTo();
t = t1;
goto Lagain;
}
if (t1n.ty == Tclass && t2n.ty == Tclass)
{
ClassDeclaration cd1 = t1n.isClassHandle();
ClassDeclaration cd2 = t2n.isClassHandle();
int offset;
if (cd1.isBaseOf(cd2, &offset))
{
if (offset)
e2 = e2.castTo(sc, t);
return Lret(t);
}
if (cd2.isBaseOf(cd1, &offset))
{
if (offset)
e1 = e1.castTo(sc, t2);
return Lret(t2);
}
return null;
}
t1 = t1n.constOf().pointerTo();
t2 = t2n.constOf().pointerTo();
if (t1.implicitConvTo(t2))
return convert(e1, t2);
if (t2.implicitConvTo(t1))
return convert(e2, t1);
return null;
}
if ((t1.ty == Tsarray || t1.ty == Tarray) && (e2.op == EXP.null_ && t2.ty == Tpointer && t2.nextOf().ty == Tvoid || e2.op == EXP.arrayLiteral && t2.ty == Tsarray && t2.nextOf().ty == Tvoid && t2.isTypeSArray().dim.toInteger() == 0 || isVoidArrayLiteral(e2, t1)))
{
/* (T[n] op void*) => T[]
* (T[] op void*) => T[]
* (T[n] op void[0]) => T[]
* (T[] op void[0]) => T[]
* (T[n] op void[]) => T[]
* (T[] op void[]) => T[]
*/
return coerce(t1.nextOf().arrayOf());
}
if ((t2.ty == Tsarray || t2.ty == Tarray) && (e1.op == EXP.null_ && t1.ty == Tpointer && t1.nextOf().ty == Tvoid || e1.op == EXP.arrayLiteral && t1.ty == Tsarray && t1.nextOf().ty == Tvoid && t1.isTypeSArray().dim.toInteger() == 0 || isVoidArrayLiteral(e1, t2)))
{
/* (void* op T[n]) => T[]
* (void* op T[]) => T[]
* (void[0] op T[n]) => T[]
* (void[0] op T[]) => T[]
* (void[] op T[n]) => T[]
* (void[] op T[]) => T[]
*/
return coerce(t2.nextOf().arrayOf());
}
if ((t1.ty == Tsarray || t1.ty == Tarray) && (m = t1.implicitConvTo(t2)) != MATCH.nomatch)
{
// https://issues.dlang.org/show_bug.cgi?id=7285
// Tsarray op [x, y, ...] should to be Tsarray
// https://issues.dlang.org/show_bug.cgi?id=14737
// Tsarray ~ [x, y, ...] should to be Tarray
if (t1.ty == Tsarray && e2.op == EXP.arrayLiteral && op != EXP.concatenate)
return convert(e2, t1);
if (m == MATCH.constant && (op == EXP.addAssign || op == EXP.minAssign || op == EXP.mulAssign || op == EXP.divAssign || op == EXP.modAssign || op == EXP.powAssign || op == EXP.andAssign || op == EXP.orAssign || op == EXP.xorAssign))
{
// Don't make the lvalue const
return Lret(t2);
}
return convert(e1, t2);
}
if ((t2.ty == Tsarray || t2.ty == Tarray) && t2.implicitConvTo(t1))
{
// https://issues.dlang.org/show_bug.cgi?id=7285
// https://issues.dlang.org/show_bug.cgi?id=14737
if (t2.ty == Tsarray && e1.op == EXP.arrayLiteral && op != EXP.concatenate)
return convert(e1, t2);
return convert(e2, t1);
}
if ((t1.ty == Tsarray || t1.ty == Tarray || t1.ty == Tpointer) && (t2.ty == Tsarray || t2.ty == Tarray || t2.ty == Tpointer) && t1.nextOf().mod != t2.nextOf().mod)
{
/* If one is mutable and the other immutable, then retry
* with both of them as const
*/
Type t1n = t1.nextOf();
Type t2n = t2.nextOf();
ubyte mod;
if (e1.op == EXP.null_ && e2.op != EXP.null_)
mod = t2n.mod;
else if (e1.op != EXP.null_ && e2.op == EXP.null_)
mod = t1n.mod;
else if (!t1n.isImmutable() && !t2n.isImmutable() && t1n.isShared() != t2n.isShared())
return null;
else
mod = MODmerge(t1n.mod, t2n.mod);
if (t1.ty == Tpointer)
t1 = t1n.castMod(mod).pointerTo();
else
t1 = t1n.castMod(mod).arrayOf();
if (t2.ty == Tpointer)
t2 = t2n.castMod(mod).pointerTo();
else
t2 = t2n.castMod(mod).arrayOf();
t = t1;
goto Lagain;
}
if (t1.ty == Tclass && t2.ty == Tclass)
{
if (t1.mod != t2.mod)
{
ubyte mod;
if (e1.op == EXP.null_ && e2.op != EXP.null_)
mod = t2.mod;
else if (e1.op != EXP.null_ && e2.op == EXP.null_)
mod = t1.mod;
else if (!t1.isImmutable() && !t2.isImmutable() && t1.isShared() != t2.isShared())
return null;
else
mod = MODmerge(t1.mod, t2.mod);
t1 = t1.castMod(mod);
t2 = t2.castMod(mod);
t = t1;
goto Lagain;
}
goto Lcc;
}
if (t1.ty == Tclass || t2.ty == Tclass)
{
Lcc:
while (1)
{
MATCH i1woat = MATCH.exact;
MATCH i2woat = MATCH.exact;
if (auto t2c = t2.isTypeClass())
i1woat = t2c.implicitConvToWithoutAliasThis(t1);
if (auto t1c = t1.isTypeClass())
i2woat = t1c.implicitConvToWithoutAliasThis(t2);
MATCH i1 = e2.implicitConvTo(t1);
MATCH i2 = e1.implicitConvTo(t2);
if (i1 && i2)
{
// We have the case of class vs. void*, so pick class
if (t1.ty == Tpointer)
i1 = MATCH.nomatch;
else if (t2.ty == Tpointer)
i2 = MATCH.nomatch;
}
// Match but without 'alias this' on classes
if (i2 && i2woat)
return coerce(t2);
if (i1 && i1woat)
return coerce(t1);
// Here use implicitCastTo() instead of castTo() to try 'alias this' on classes
Type coerceImplicit(Type towards)
{
e1 = e1.implicitCastTo(sc, towards);
e2 = e2.implicitCastTo(sc, towards);
return Lret(towards);
}
// Implicit conversion with 'alias this'
if (i2)
return coerceImplicit(t2);
if (i1)
return coerceImplicit(t1);
if (t1.ty == Tclass && t2.ty == Tclass)
{
TypeClass tc1 = t1.isTypeClass();
TypeClass tc2 = t2.isTypeClass();
/* Pick 'tightest' type
*/
ClassDeclaration cd1 = tc1.sym.baseClass;
ClassDeclaration cd2 = tc2.sym.baseClass;
if (cd1 && cd2)
{
t1 = cd1.type.castMod(t1.mod);
t2 = cd2.type.castMod(t2.mod);
}
else if (cd1)
t1 = cd1.type;
else if (cd2)
t2 = cd2.type;
else
return null;
}
else if (t1.ty == Tstruct && t1.isTypeStruct().sym.aliasthis)
{
if (isRecursiveAliasThis(att1, e1.type))
return null;
//printf("att tmerge(c || c) e1 = %s\n", e1.type.toChars());
e1 = resolveAliasThis(sc, e1);
t1 = e1.type;
continue;
}
else if (t2.ty == Tstruct && t2.isTypeStruct().sym.aliasthis)
{
if (isRecursiveAliasThis(att2, e2.type))
return null;
//printf("att tmerge(c || c) e2 = %s\n", e2.type.toChars());
e2 = resolveAliasThis(sc, e2);
t2 = e2.type;
continue;
}
else
return null;
}
}
if (t1.ty == Tstruct && t2.ty == Tstruct)
{
if (t1.mod != t2.mod)
{
if (!t1.isImmutable() && !t2.isImmutable() && t1.isShared() != t2.isShared())
return null;
ubyte mod = MODmerge(t1.mod, t2.mod);
t1 = t1.castMod(mod);
t2 = t2.castMod(mod);
t = t1;
goto Lagain;
}
TypeStruct ts1 = t1.isTypeStruct();
TypeStruct ts2 = t2.isTypeStruct();
if (ts1.sym != ts2.sym)
{
if (!ts1.sym.aliasthis && !ts2.sym.aliasthis)
return null;
MATCH i1 = MATCH.nomatch;
MATCH i2 = MATCH.nomatch;
Expression e1b = null;
Expression e2b = null;
if (ts2.sym.aliasthis)
{
if (isRecursiveAliasThis(att2, e2.type))
return null;
//printf("att tmerge(s && s) e2 = %s\n", e2.type.toChars());
e2b = resolveAliasThis(sc, e2);
i1 = e2b.implicitConvTo(t1);
}
if (ts1.sym.aliasthis)
{
if (isRecursiveAliasThis(att1, e1.type))
return null;
//printf("att tmerge(s && s) e1 = %s\n", e1.type.toChars());
e1b = resolveAliasThis(sc, e1);
i2 = e1b.implicitConvTo(t2);
}
if (i1 && i2)
return null;
if (i1)
return convert(e2, t1);
if (i2)
return convert(e1, t2);
if (e1b)
{
e1 = e1b;
t1 = e1b.type.toBasetype();
}
if (e2b)
{
e2 = e2b;
t2 = e2b.type.toBasetype();
}
t = t1;
goto Lagain;
}
}
if (t1.ty == Tstruct && t1.isTypeStruct().sym.aliasthis)
{
if (isRecursiveAliasThis(att1, e1.type))
return null;
//printf("att tmerge(s || s) e1 = %s\n", e1.type.toChars());
e1 = resolveAliasThis(sc, e1);
t1 = e1.type;
t = t1;
goto Lagain;
}
if (t2.ty == Tstruct && t2.isTypeStruct().sym.aliasthis)
{
if (isRecursiveAliasThis(att2, e2.type))
return null;
//printf("att tmerge(s || s) e2 = %s\n", e2.type.toChars());
e2 = resolveAliasThis(sc, e2);
t2 = e2.type;
t = t2;
goto Lagain;
}
if ((e1.op == EXP.string_ || e1.op == EXP.null_) && e1.implicitConvTo(t2))
return convert(e1, t2);
if ((e2.op == EXP.string_ || e2.op == EXP.null_) && e2.implicitConvTo(t1))
return convert(e2, t1);
if (t1.ty == Tsarray && t2.ty == Tsarray && e2.implicitConvTo(t1.nextOf().arrayOf()))
return coerce(t1.nextOf().arrayOf());
if (t1.ty == Tsarray && t2.ty == Tsarray && e1.implicitConvTo(t2.nextOf().arrayOf()))
return coerce(t2.nextOf().arrayOf());
if (t1.ty == Tvector && t2.ty == Tvector)
{
// https://issues.dlang.org/show_bug.cgi?id=13841
// all vector types should have no common types between
// different vectors, even though their sizes are same.
auto tv1 = t1.isTypeVector();
auto tv2 = t2.isTypeVector();
if (!tv1.basetype.equals(tv2.basetype))
return null;
goto LmodCompare;
}
if (t1.ty == Tvector && t2.ty != Tvector && e2.implicitConvTo(t1))
{
e2 = e2.castTo(sc, t1);
t2 = t1;
t = t1;
goto Lagain;
}
if (t2.ty == Tvector && t1.ty != Tvector && e1.implicitConvTo(t2))
{
e1 = e1.castTo(sc, t2);
t1 = t2;
t = t1;
goto Lagain;
}
if (t1.isintegral() && t2.isintegral())
{
if (t1.ty != t2.ty)
{
if (t1.ty == Tvector || t2.ty == Tvector)
return null;
e1 = integralPromotions(e1, sc);
e2 = integralPromotions(e2, sc);
t1 = e1.type;
t2 = e2.type;
goto Lagain;
}
assert(t1.ty == t2.ty);
LmodCompare:
if (!t1.isImmutable() && !t2.isImmutable() && t1.isShared() != t2.isShared())
return null;
ubyte mod = MODmerge(t1.mod, t2.mod);
t1 = t1.castMod(mod);
t2 = t2.castMod(mod);
t = t1;
e1 = e1.castTo(sc, t);
e2 = e2.castTo(sc, t);
goto Lagain;
}
if (t1.ty == Tnull && t2.ty == Tnull)
{
ubyte mod = MODmerge(t1.mod, t2.mod);
return coerce(t1.castMod(mod));
}
if (t2.ty == Tnull && (t1.ty == Tpointer || t1.ty == Taarray || t1.ty == Tarray))
return convert(e2, t1);
if (t1.ty == Tnull && (t2.ty == Tpointer || t2.ty == Taarray || t2.ty == Tarray))
return convert(e1, t2);
/// Covers array operations for user-defined types
Type checkArrayOpType(Expression e1, Expression e2, EXP op, Scope *sc)
{
// scalar op scalar - we shouldn't be here
if (e1.type.ty != Tarray && e1.type.ty != Tsarray && e2.type.ty != Tarray && e2.type.ty != Tsarray)
return null;
// only supporting slices and array literals
if (!e1.isSliceExp() && !e1.isArrayLiteralExp() && !e2.isSliceExp() && !e2.isArrayLiteralExp())
return null;
// start with e1 op e2 and if either one of e1 or e2 is a slice or array literal,
// replace it with the first element of the array
Expression lhs = e1;
Expression rhs = e2;
// T[x .. y] op ?
if (auto se1 = e1.isSliceExp())
lhs = new IndexExp(Loc.initial, se1.e1, IntegerExp.literal!0);
// [t1, t2, .. t3] op ?
if (auto ale1 = e1.isArrayLiteralExp())
lhs = ale1.opIndex(0);
// ? op U[z .. t]
if (auto se2 = e2.isSliceExp())
rhs = new IndexExp(Loc.initial, se2.e1, IntegerExp.literal!0);
// ? op [u1, u2, .. u3]
if (auto ale2 = e2.isArrayLiteralExp())
rhs = ale2.opIndex(0);
// create a new binary expression with the new lhs and rhs (at this stage, at least
// one of lhs/rhs has been replaced with the 0'th element of the array it was before)
Expression exp;
switch (op)
{
case EXP.add:
exp = new AddExp(Loc.initial, lhs, rhs); break;
case EXP.min:
exp = new MinExp(Loc.initial, lhs, rhs); break;
case EXP.mul:
exp = new MulExp(Loc.initial, lhs, rhs); break;
case EXP.div:
exp = new DivExp(Loc.initial, lhs, rhs); break;
case EXP.pow:
exp = new PowExp(Loc.initial, lhs, rhs); break;
default:
exp = null;
}
if (exp)
{
// if T op U is valid and has type V
// then T[] op U and T op U[] should be valid and have type V[]
Expression e = exp.trySemantic(sc);
if (e && e.type)
return e.type.arrayOf;
}
return null;
}
if (t1.ty == Tarray && isBinArrayOp(op) && isArrayOpOperand(e1))
{
if (e2.implicitConvTo(t1.nextOf()))
{
// T[] op T
// T[] op cast(T)U
e2 = e2.castTo(sc, t1.nextOf());
return Lret(t1.nextOf().arrayOf());
}
if (t1.nextOf().implicitConvTo(e2.type))
{
// (cast(T)U)[] op T (https://issues.dlang.org/show_bug.cgi?id=12780)
// e1 is left as U[], it will be handled in arrayOp() later.
return Lret(e2.type.arrayOf());
}
if (t2.ty == Tarray && isArrayOpOperand(e2))
{
if (t1.nextOf().implicitConvTo(t2.nextOf()))
{
// (cast(T)U)[] op T[] (https://issues.dlang.org/show_bug.cgi?id=12780)
t = t2.nextOf().arrayOf();
// if cast won't be handled in arrayOp() later
if (!isArrayOpImplicitCast(t1.isTypeDArray(), t2.isTypeDArray()))
e1 = e1.castTo(sc, t);
return Lret(t);
}
if (t2.nextOf().implicitConvTo(t1.nextOf()))
{
// T[] op (cast(T)U)[] (https://issues.dlang.org/show_bug.cgi?id=12780)
// e2 is left as U[], it will be handled in arrayOp() later.
t = t1.nextOf().arrayOf();
// if cast won't be handled in arrayOp() later
if (!isArrayOpImplicitCast(t2.isTypeDArray(), t1.isTypeDArray()))
e2 = e2.castTo(sc, t);
return Lret(t);
}
}
t = checkArrayOpType(e1, e2, op, sc);
if (t !is null)
return Lret(t);
return null;
}
else if (t2.ty == Tarray && isBinArrayOp(op) && isArrayOpOperand(e2))
{
if (e1.implicitConvTo(t2.nextOf()))
{
// T op T[]
// cast(T)U op T[]
e1 = e1.castTo(sc, t2.nextOf());
t = t2.nextOf().arrayOf();
}
else if (t2.nextOf().implicitConvTo(e1.type))
{
// T op (cast(T)U)[] (https://issues.dlang.org/show_bug.cgi?id=12780)
// e2 is left as U[], it will be handled in arrayOp() later.
t = e1.type.arrayOf();
}
else
{
t = checkArrayOpType(e1, e2, op, sc);
if (t is null)
return null;
}
//printf("test %s\n", EXPtoString(op).ptr);
e1 = e1.optimize(WANTvalue);
if (isCommutative(op) && e1.isConst())
{
/* Swap operands to minimize number of functions generated
*/
//printf("swap %s\n", EXPtoString(op).ptr);
Expression tmp = e1;
e1 = e2;
e2 = tmp;
}
return Lret(t);
}
return null;
}
/************************************
* Bring leaves to common type.
* Returns:
* null on success, ErrorExp if error occurs
*/
Expression typeCombine(BinExp be, Scope* sc)
{
Expression errorReturn()
{
Expression ex = be.incompatibleTypes();
if (ex.op == EXP.error)
return ex;
return ErrorExp.get();
}
Type t1 = be.e1.type.toBasetype();
Type t2 = be.e2.type.toBasetype();
if (be.op == EXP.min || be.op == EXP.add)
{
// struct+struct, and class+class are errors
if (t1.ty == Tstruct && t2.ty == Tstruct)
return errorReturn();
else if (t1.ty == Tclass && t2.ty == Tclass)
return errorReturn();
else if (t1.ty == Taarray && t2.ty == Taarray)
return errorReturn();
}
if (auto result = typeMerge(sc, be.op, be.e1, be.e2))
{
if (be.type is null)
be.type = result;
}
else
return errorReturn();
// If the types have no value, return an error
if (be.e1.op == EXP.error)
return be.e1;
if (be.e2.op == EXP.error)
return be.e2;
return null;
}
/***********************************
* Do integral promotions (convertchk).
* Don't convert <array of> to <pointer to>
*/
Expression integralPromotions(Expression e, Scope* sc)
{
//printf("integralPromotions %s %s\n", e.toChars(), e.type.toChars());
switch (e.type.toBasetype().ty)
{
case Tvoid:
e.error("void has no value");
return ErrorExp.get();
case Tint8:
case Tuns8:
case Tint16:
case Tuns16:
case Tbool:
case Tchar:
case Twchar:
e = e.castTo(sc, Type.tint32);
break;
case Tdchar:
e = e.castTo(sc, Type.tuns32);
break;
default:
break;
}
return e;
}
/******************************************************
* This provides a transition from the non-promoting behavior
* of unary + - ~ to the C-like integral promotion behavior.
* Params:
* sc = context
* ue = NegExp, UAddExp, or ComExp which is revised per rules
* References:
* https://issues.dlang.org/show_bug.cgi?id=16997
*/
void fix16997(Scope* sc, UnaExp ue)
{
if (global.params.fix16997 || sc.flags & SCOPE.Cfile)
ue.e1 = integralPromotions(ue.e1, sc); // desired C-like behavor
else
{
switch (ue.e1.type.toBasetype.ty)
{
case Tint8:
case Tuns8:
case Tint16:
case Tuns16:
//case Tbool: // these operations aren't allowed on bool anyway
case Tchar:
case Twchar:
case Tdchar:
ue.deprecation("integral promotion not done for `%s`, remove '-revert=intpromote' switch or `%scast(int)(%s)`",
ue.toChars(), EXPtoString(ue.op).ptr, ue.e1.toChars());
break;
default:
break;
}
}
}
/***********************************
* See if both types are arrays that can be compared
* for equality without any casting. Return true if so.
* This is to enable comparing things like an immutable
* array with a mutable one.
*/
extern (C++) bool arrayTypeCompatibleWithoutCasting(Type t1, Type t2)
{
t1 = t1.toBasetype();
t2 = t2.toBasetype();
if ((t1.ty == Tarray || t1.ty == Tsarray || t1.ty == Tpointer) && t2.ty == t1.ty)
{
if (t1.nextOf().implicitConvTo(t2.nextOf()) >= MATCH.constant || t2.nextOf().implicitConvTo(t1.nextOf()) >= MATCH.constant)
return true;
}
return false;
}
/******************************************************************/
/* Determine the integral ranges of an expression.
* This is used to determine if implicit narrowing conversions will
* be allowed.
*/
@trusted
IntRange getIntRange(Expression e)
{
IntRange visit(Expression e)
{
return IntRange.fromType(e.type);
}
IntRange visitInteger(IntegerExp e)
{
return IntRange(SignExtendedNumber(e.getInteger()))._cast(e.type);
}
IntRange visitCast(CastExp e)
{
return getIntRange(e.e1)._cast(e.type);
}
IntRange visitAdd(AddExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 + ir2)._cast(e.type);
}
IntRange visitMin(MinExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 - ir2)._cast(e.type);
}
IntRange visitDiv(DivExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 / ir2)._cast(e.type);
}
IntRange visitMul(MulExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 * ir2)._cast(e.type);
}
IntRange visitMod(ModExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
// Modding on 0 is invalid anyway.
if (!ir2.absNeg().imin.negative)
{
return visit(e);
}
return (ir1 % ir2)._cast(e.type);
}
IntRange visitAnd(AndExp e)
{
IntRange result;
bool hasResult = false;
result.unionOrAssign(getIntRange(e.e1) & getIntRange(e.e2), hasResult);
assert(hasResult);
return result._cast(e.type);
}
IntRange visitOr(OrExp e)
{
IntRange result;
bool hasResult = false;
result.unionOrAssign(getIntRange(e.e1) | getIntRange(e.e2), hasResult);
assert(hasResult);
return result._cast(e.type);
}
IntRange visitXor(XorExp e)
{
IntRange result;
bool hasResult = false;
result.unionOrAssign(getIntRange(e.e1) ^ getIntRange(e.e2), hasResult);
assert(hasResult);
return result._cast(e.type);
}
IntRange visitShl(ShlExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 << ir2)._cast(e.type);
}
IntRange visitShr(ShrExp e)
{
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return (ir1 >> ir2)._cast(e.type);
}
IntRange visitUshr(UshrExp e)
{
IntRange ir1 = getIntRange(e.e1).castUnsigned(e.e1.type);
IntRange ir2 = getIntRange(e.e2);
return (ir1 >>> ir2)._cast(e.type);
}
IntRange visitAssign(AssignExp e)
{
return getIntRange(e.e2)._cast(e.type);
}
IntRange visitCond(CondExp e)
{
// No need to check e.econd; assume caller has called optimize()
IntRange ir1 = getIntRange(e.e1);
IntRange ir2 = getIntRange(e.e2);
return ir1.unionWith(ir2)._cast(e.type);
}
IntRange visitVar(VarExp e)
{
Expression ie;
VarDeclaration vd = e.var.isVarDeclaration();
if (vd && vd.range)
return vd.range._cast(e.type);
else if (vd && vd._init && !vd.type.isMutable() && (ie = vd.getConstInitializer()) !is null)
return getIntRange(ie);
else
return visit(e);
}
IntRange visitComma(CommaExp e)
{
return getIntRange(e.e2);
}
IntRange visitCom(ComExp e)
{
IntRange ir = getIntRange(e.e1);
return IntRange(SignExtendedNumber(~ir.imax.value, !ir.imax.negative), SignExtendedNumber(~ir.imin.value, !ir.imin.negative))._cast(e.type);
}
IntRange visitNeg(NegExp e)
{
IntRange ir = getIntRange(e.e1);
return (-ir)._cast(e.type);
}
switch (e.op)
{
default : return visit(e);
case EXP.int64 : return visitInteger(e.isIntegerExp());
case EXP.cast_ : return visitCast(e.isCastExp());
case EXP.add : return visitAdd(e.isAddExp());
case EXP.min : return visitMin(e.isMinExp());
case EXP.div : return visitDiv(e.isDivExp());
case EXP.mul : return visitMul(e.isMulExp());
case EXP.mod : return visitMod(e.isModExp());
case EXP.and : return visitAnd(e.isAndExp());
case EXP.or : return visitOr(e.isOrExp());
case EXP.xor : return visitXor(e.isXorExp());
case EXP.leftShift : return visitShl(e.isShlExp());
case EXP.rightShift : return visitShr(e.isShrExp());
case EXP.unsignedRightShift : return visitUshr(e.isUshrExp());
case EXP.blit : return visitAssign(e.isBlitExp());
case EXP.construct : return visitAssign(e.isConstructExp());
case EXP.assign : return visitAssign(e.isAssignExp());
case EXP.question : return visitCond(e.isCondExp());
case EXP.variable : return visitVar(e.isVarExp());
case EXP.comma : return visitComma(e.isCommaExp());
case EXP.tilde : return visitCom(e.isComExp());
case EXP.negate : return visitNeg(e.isNegExp());
}
}
/**
* A helper function to "cast" from expressions of type noreturn to
* any other type - noreturn is implicitly convertible to any other type.
* However, the dmd backend does not like a naive cast from a noreturn expression
* (particularly an `assert(0)`) so this function generates:
*
* `(assert(0), value)` instead of `cast(to)(assert(0))`.
*
* `value` is currently `to.init` however it cannot be read so could be made simpler.
* Params:
* toBeCasted = Expression of type noreturn to cast
* to = Type to cast the expression to.
* Returns: A CommaExp, upon any failure ErrorExp will be returned.
*/
Expression specialNoreturnCast(Expression toBeCasted, Type to)
{
return Expression.combine(toBeCasted, to.defaultInitLiteral(toBeCasted.loc));
}