| /** |
| * Semantic analysis of expressions. |
| * |
| * 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/expressionsem.d, _expressionsem.d) |
| * Documentation: https://dlang.org/phobos/dmd_expressionsem.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/expressionsem.d |
| */ |
| |
| module dmd.expressionsem; |
| |
| import core.stdc.stdio; |
| |
| import dmd.access; |
| import dmd.aggregate; |
| import dmd.aliasthis; |
| import dmd.arrayop; |
| import dmd.arraytypes; |
| import dmd.attrib; |
| import dmd.astcodegen; |
| import dmd.astenums; |
| import dmd.canthrow; |
| import dmd.chkformat; |
| import dmd.ctorflow; |
| import dmd.dscope; |
| import dmd.dsymbol; |
| import dmd.declaration; |
| import dmd.dclass; |
| import dmd.dcast; |
| import dmd.delegatize; |
| import dmd.denum; |
| import dmd.dimport; |
| import dmd.dinterpret; |
| import dmd.dmangle; |
| import dmd.dmodule; |
| import dmd.dstruct; |
| import dmd.dsymbolsem; |
| import dmd.dtemplate; |
| import dmd.errors; |
| import dmd.escape; |
| import dmd.expression; |
| import dmd.file_manager; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.hdrgen; |
| import dmd.id; |
| import dmd.identifier; |
| import dmd.imphint; |
| import dmd.importc; |
| import dmd.init; |
| import dmd.initsem; |
| import dmd.inline; |
| import dmd.intrange; |
| import dmd.mtype; |
| import dmd.mustuse; |
| import dmd.nspace; |
| import dmd.opover; |
| import dmd.optimize; |
| import dmd.parse; |
| import dmd.printast; |
| import dmd.root.array; |
| import dmd.root.ctfloat; |
| import dmd.root.file; |
| import dmd.root.filename; |
| import dmd.common.outbuffer; |
| import dmd.root.rootobject; |
| import dmd.root.string; |
| import dmd.root.utf; |
| import dmd.semantic2; |
| import dmd.semantic3; |
| import dmd.sideeffect; |
| import dmd.safe; |
| import dmd.target; |
| import dmd.tokens; |
| import dmd.traits; |
| import dmd.typesem; |
| import dmd.typinf; |
| import dmd.utils; |
| import dmd.visitor; |
| |
| enum LOGSEMANTIC = false; |
| |
| /******************************************************** |
| * Perform semantic analysis and CTFE on expressions to produce |
| * a string. |
| * Params: |
| * buf = append generated string to buffer |
| * sc = context |
| * exps = array of Expressions |
| * Returns: |
| * true on error |
| */ |
| bool expressionsToString(ref OutBuffer buf, Scope* sc, Expressions* exps) |
| { |
| if (!exps) |
| return false; |
| |
| foreach (ex; *exps) |
| { |
| if (!ex) |
| continue; |
| auto sc2 = sc.startCTFE(); |
| auto e2 = ex.expressionSemantic(sc2); |
| auto e3 = resolveProperties(sc2, e2); |
| sc2.endCTFE(); |
| |
| // allowed to contain types as well as expressions |
| auto e4 = ctfeInterpretForPragmaMsg(e3); |
| if (!e4 || e4.op == EXP.error) |
| return true; |
| |
| // expand tuple |
| if (auto te = e4.isTupleExp()) |
| { |
| if (expressionsToString(buf, sc, te.exps)) |
| return true; |
| continue; |
| } |
| // char literals exp `.toStringExp` return `null` but we cant override it |
| // because in most contexts we don't want the conversion to succeed. |
| IntegerExp ie = e4.isIntegerExp(); |
| const ty = (ie && ie.type) ? ie.type.ty : Terror; |
| if (ty.isSomeChar) |
| { |
| auto tsa = new TypeSArray(ie.type, IntegerExp.literal!1); |
| e4 = new ArrayLiteralExp(ex.loc, tsa, ie); |
| } |
| |
| if (StringExp se = e4.toStringExp()) |
| buf.writestring(se.toUTF8(sc).peekString()); |
| else |
| buf.writestring(e4.toString()); |
| } |
| return false; |
| } |
| |
| |
| /*********************************************************** |
| * Resolve `exp` as a compile-time known string. |
| * Params: |
| * sc = scope |
| * exp = Expression which expected as a string |
| * s = What the string is expected for, will be used in error diagnostic. |
| * Returns: |
| * String literal, or `null` if error happens. |
| */ |
| StringExp semanticString(Scope *sc, Expression exp, const char* s) |
| { |
| sc = sc.startCTFE(); |
| exp = exp.expressionSemantic(sc); |
| exp = resolveProperties(sc, exp); |
| sc = sc.endCTFE(); |
| |
| if (exp.op == EXP.error) |
| return null; |
| |
| auto e = exp; |
| if (exp.type.isString()) |
| { |
| e = e.ctfeInterpret(); |
| if (e.op == EXP.error) |
| return null; |
| } |
| |
| auto se = e.toStringExp(); |
| if (!se) |
| { |
| exp.error("`string` expected for %s, not `(%s)` of type `%s`", |
| s, exp.toChars(), exp.type.toChars()); |
| return null; |
| } |
| return se; |
| } |
| |
| private Expression extractOpDollarSideEffect(Scope* sc, UnaExp ue) |
| { |
| Expression e0; |
| Expression e1 = Expression.extractLast(ue.e1, e0); |
| // https://issues.dlang.org/show_bug.cgi?id=12585 |
| // Extract the side effect part if ue.e1 is comma. |
| |
| if ((sc.flags & SCOPE.ctfe) ? hasSideEffect(e1) : !isTrivialExp(e1)) // match logic in extractSideEffect() |
| { |
| /* Even if opDollar is needed, 'e1' should be evaluate only once. So |
| * Rewrite: |
| * e1.opIndex( ... use of $ ... ) |
| * e1.opSlice( ... use of $ ... ) |
| * as: |
| * (ref __dop = e1, __dop).opIndex( ... __dop.opDollar ...) |
| * (ref __dop = e1, __dop).opSlice( ... __dop.opDollar ...) |
| */ |
| e1 = extractSideEffect(sc, "__dop", e0, e1, false); |
| assert(e1.isVarExp()); |
| e1.isVarExp().var.storage_class |= STC.exptemp; // lifetime limited to expression |
| } |
| ue.e1 = e1; |
| return e0; |
| } |
| |
| /************************************** |
| * Runs semantic on ae.arguments. Declares temporary variables |
| * if '$' was used. |
| */ |
| Expression resolveOpDollar(Scope* sc, ArrayExp ae, Expression* pe0) |
| { |
| assert(!ae.lengthVar); |
| *pe0 = null; |
| AggregateDeclaration ad = isAggregate(ae.e1.type); |
| Dsymbol slice = search_function(ad, Id.slice); |
| //printf("slice = %s %s\n", slice.kind(), slice.toChars()); |
| foreach (i, e; *ae.arguments) |
| { |
| if (i == 0) |
| *pe0 = extractOpDollarSideEffect(sc, ae); |
| |
| if (e.op == EXP.interval && !(slice && slice.isTemplateDeclaration())) |
| { |
| Lfallback: |
| if (ae.arguments.dim == 1) |
| return null; |
| ae.error("multi-dimensional slicing requires template `opSlice`"); |
| return ErrorExp.get(); |
| } |
| //printf("[%d] e = %s\n", i, e.toChars()); |
| |
| // Create scope for '$' variable for this dimension |
| auto sym = new ArrayScopeSymbol(sc, ae); |
| sym.parent = sc.scopesym; |
| sc = sc.push(sym); |
| ae.lengthVar = null; // Create it only if required |
| ae.currentDimension = i; // Dimension for $, if required |
| |
| e = e.expressionSemantic(sc); |
| e = resolveProperties(sc, e); |
| |
| if (ae.lengthVar && sc.func) |
| { |
| // If $ was used, declare it now |
| Expression de = new DeclarationExp(ae.loc, ae.lengthVar); |
| de = de.expressionSemantic(sc); |
| *pe0 = Expression.combine(*pe0, de); |
| } |
| sc = sc.pop(); |
| |
| if (auto ie = e.isIntervalExp()) |
| { |
| auto tiargs = new Objects(); |
| Expression edim = new IntegerExp(ae.loc, i, Type.tsize_t); |
| edim = edim.expressionSemantic(sc); |
| tiargs.push(edim); |
| |
| auto fargs = new Expressions(2); |
| (*fargs)[0] = ie.lwr; |
| (*fargs)[1] = ie.upr; |
| |
| uint xerrors = global.startGagging(); |
| sc = sc.push(); |
| FuncDeclaration fslice = resolveFuncCall(ae.loc, sc, slice, tiargs, ae.e1.type, fargs, FuncResolveFlag.quiet); |
| sc = sc.pop(); |
| global.endGagging(xerrors); |
| if (!fslice) |
| goto Lfallback; |
| |
| e = new DotTemplateInstanceExp(ae.loc, ae.e1, slice.ident, tiargs); |
| e = new CallExp(ae.loc, e, fargs); |
| e = e.expressionSemantic(sc); |
| } |
| |
| if (!e.type) |
| { |
| ae.error("`%s` has no value", e.toChars()); |
| e = ErrorExp.get(); |
| } |
| if (e.op == EXP.error) |
| return e; |
| |
| (*ae.arguments)[i] = e; |
| } |
| return ae; |
| } |
| |
| /************************************** |
| * Runs semantic on se.lwr and se.upr. Declares a temporary variable |
| * if '$' was used. |
| * Returns: |
| * ae, or ErrorExp if errors occurred |
| */ |
| Expression resolveOpDollar(Scope* sc, ArrayExp ae, IntervalExp ie, Expression* pe0) |
| { |
| //assert(!ae.lengthVar); |
| if (!ie) |
| return ae; |
| |
| VarDeclaration lengthVar = ae.lengthVar; |
| bool errors = false; |
| |
| // create scope for '$' |
| auto sym = new ArrayScopeSymbol(sc, ae); |
| sym.parent = sc.scopesym; |
| sc = sc.push(sym); |
| |
| Expression sem(Expression e) |
| { |
| e = e.expressionSemantic(sc); |
| e = resolveProperties(sc, e); |
| if (!e.type) |
| { |
| ae.error("`%s` has no value", e.toChars()); |
| errors = true; |
| } |
| return e; |
| } |
| |
| ie.lwr = sem(ie.lwr); |
| ie.upr = sem(ie.upr); |
| |
| if (ie.lwr.isErrorExp() || ie.upr.isErrorExp()) |
| errors = true; |
| |
| if (lengthVar != ae.lengthVar && sc.func) |
| { |
| // If $ was used, declare it now |
| Expression de = new DeclarationExp(ae.loc, ae.lengthVar); |
| de = de.expressionSemantic(sc); |
| *pe0 = Expression.combine(*pe0, de); |
| } |
| |
| sc = sc.pop(); |
| |
| return errors ? ErrorExp.get() : ae; |
| } |
| |
| /****************************** |
| * Perform semantic() on an array of Expressions. |
| */ |
| extern(D) bool arrayExpressionSemantic( |
| Expression[] exps, Scope* sc, bool preserveErrors = false) |
| { |
| bool err = false; |
| foreach (ref e; exps) |
| { |
| if (e is null) continue; |
| auto e2 = e.expressionSemantic(sc); |
| if (e2.op == EXP.error) |
| err = true; |
| if (preserveErrors || e2.op != EXP.error) |
| e = e2; |
| } |
| return err; |
| } |
| |
| /* |
| Checks if `exp` contains a direct access to a `noreturn` |
| variable. If that is the case, an `assert(0)` expression |
| is generated and returned. This function should be called |
| only after semantic analysis has been performed on `exp`. |
| |
| Params: |
| exp = expression that is checked |
| |
| Returns: |
| An `assert(0)` expression if `exp` contains a `noreturn` |
| variable access, `exp` otherwise. |
| */ |
| |
| Expression checkNoreturnVarAccess(Expression exp) |
| { |
| assert(exp.type); |
| |
| Expression result = exp; |
| if (exp.type.isTypeNoreturn() && !exp.isAssertExp() && |
| !exp.isThrowExp() && !exp.isCallExp()) |
| { |
| auto msg = new StringExp(exp.loc, "Accessed expression of type `noreturn`"); |
| msg.type = Type.tstring; |
| result = new AssertExp(exp.loc, IntegerExp.literal!0, msg); |
| result.type = exp.type; |
| } |
| |
| return result; |
| } |
| |
| /****************************** |
| * Check the tail CallExp is really property function call. |
| * Bugs: |
| * This doesn't appear to do anything. |
| */ |
| private bool checkPropertyCall(Expression e) |
| { |
| e = lastComma(e); |
| |
| if (auto ce = e.isCallExp()) |
| { |
| if (ce.f) |
| { |
| auto tf = ce.f.type.isTypeFunction(); |
| /* If a forward reference to ce.f, try to resolve it |
| */ |
| if (!tf.deco && ce.f.semanticRun < PASS.semanticdone) |
| { |
| ce.f.dsymbolSemantic(null); |
| tf = ce.f.type.isTypeFunction(); |
| } |
| } |
| else if (!ce.e1.type.isFunction_Delegate_PtrToFunction()) |
| assert(0); |
| } |
| return false; |
| } |
| |
| /****************************** |
| * Find symbol in accordance with the UFCS name look up rule |
| */ |
| private Expression searchUFCS(Scope* sc, UnaExp ue, Identifier ident) |
| { |
| //printf("searchUFCS(ident = %s)\n", ident.toChars()); |
| Loc loc = ue.loc; |
| |
| // TODO: merge with Scope.search.searchScopes() |
| Dsymbol searchScopes(int flags) |
| { |
| Dsymbol s = null; |
| for (Scope* scx = sc; scx; scx = scx.enclosing) |
| { |
| if (!scx.scopesym) |
| continue; |
| if (scx.scopesym.isModule()) |
| flags |= SearchUnqualifiedModule; // tell Module.search() that SearchLocalsOnly is to be obeyed |
| s = scx.scopesym.search(loc, ident, flags); |
| if (s) |
| { |
| // overload set contains only module scope symbols. |
| if (s.isOverloadSet()) |
| break; |
| // selective/renamed imports also be picked up |
| if (AliasDeclaration ad = s.isAliasDeclaration()) |
| { |
| if (ad._import) |
| break; |
| } |
| // See only module scope symbols for UFCS target. |
| Dsymbol p = s.toParent2(); |
| if (p && p.isModule()) |
| break; |
| } |
| s = null; |
| |
| // Stop when we hit a module, but keep going if that is not just under the global scope |
| if (scx.scopesym.isModule() && !(scx.enclosing && !scx.enclosing.enclosing)) |
| break; |
| } |
| return s; |
| } |
| |
| int flags = 0; |
| Dsymbol s; |
| |
| if (sc.flags & SCOPE.ignoresymbolvisibility) |
| flags |= IgnoreSymbolVisibility; |
| |
| // First look in local scopes |
| s = searchScopes(flags | SearchLocalsOnly); |
| if (!s) |
| { |
| // Second look in imported modules |
| s = searchScopes(flags | SearchImportsOnly); |
| } |
| |
| if (!s) |
| return ue.e1.type.getProperty(sc, loc, ident, 0, ue.e1); |
| |
| FuncDeclaration f = s.isFuncDeclaration(); |
| if (f) |
| { |
| TemplateDeclaration td = getFuncTemplateDecl(f); |
| if (td) |
| { |
| if (td.overroot) |
| td = td.overroot; |
| s = td; |
| } |
| } |
| |
| if (auto dti = ue.isDotTemplateInstanceExp()) |
| { |
| auto ti = new TemplateInstance(loc, s.ident, dti.ti.tiargs); |
| if (!ti.updateTempDecl(sc, s)) |
| return ErrorExp.get(); |
| return new ScopeExp(loc, ti); |
| } |
| else |
| { |
| //printf("-searchUFCS() %s\n", s.toChars()); |
| return new DsymbolExp(loc, s); |
| } |
| } |
| |
| /****************************** |
| * Pull out callable entity with UFCS. |
| */ |
| private Expression resolveUFCS(Scope* sc, CallExp ce) |
| { |
| Loc loc = ce.loc; |
| Expression eleft; |
| Expression e; |
| |
| if (auto die = ce.e1.isDotIdExp()) |
| { |
| Identifier ident = die.ident; |
| |
| Expression ex = die.semanticX(sc); |
| if (ex != die) |
| { |
| ce.e1 = ex; |
| return null; |
| } |
| eleft = die.e1; |
| |
| Type t = eleft.type.toBasetype(); |
| if (t.ty == Tarray || t.ty == Tsarray || t.ty == Tnull || (t.isTypeBasic() && t.ty != Tvoid)) |
| { |
| /* Built-in types and arrays have no callable properties, so do shortcut. |
| * It is necessary in: e.init() |
| */ |
| } |
| else if (t.ty == Taarray) |
| { |
| if (ident == Id.remove) |
| { |
| /* Transform: |
| * aa.remove(arg) into delete aa[arg] |
| */ |
| if (!ce.arguments || ce.arguments.dim != 1) |
| { |
| ce.error("expected key as argument to `aa.remove()`"); |
| return ErrorExp.get(); |
| } |
| if (!eleft.type.isMutable()) |
| { |
| ce.error("cannot remove key from `%s` associative array `%s`", MODtoChars(t.mod), eleft.toChars()); |
| return ErrorExp.get(); |
| } |
| Expression key = (*ce.arguments)[0]; |
| key = key.expressionSemantic(sc); |
| key = resolveProperties(sc, key); |
| |
| TypeAArray taa = t.isTypeAArray(); |
| key = key.implicitCastTo(sc, taa.index); |
| |
| if (key.checkValue() || key.checkSharedAccess(sc)) |
| return ErrorExp.get(); |
| |
| semanticTypeInfo(sc, taa.index); |
| |
| return new RemoveExp(loc, eleft, key); |
| } |
| } |
| else |
| { |
| if (Expression ey = die.semanticY(sc, 1)) |
| { |
| if (ey.op == EXP.error) |
| return ey; |
| ce.e1 = ey; |
| if (isDotOpDispatch(ey)) |
| { |
| // even opDispatch and UFCS must have valid arguments, |
| // so now that we've seen indication of a problem, |
| // check them for issues. |
| Expressions* originalArguments = Expression.arraySyntaxCopy(ce.arguments); |
| |
| uint errors = global.startGagging(); |
| e = ce.expressionSemantic(sc); |
| if (!global.endGagging(errors)) |
| return e; |
| |
| if (arrayExpressionSemantic(originalArguments.peekSlice(), sc)) |
| return ErrorExp.get(); |
| |
| /* fall down to UFCS */ |
| } |
| else |
| return null; |
| } |
| } |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=13953 |
| * |
| * If a struct has an alias this to an associative array |
| * and remove is used on a struct instance, we have to |
| * check first if there is a remove function that can be called |
| * on the struct. If not we must check the alias this. |
| * |
| * struct A |
| * { |
| * string[string] a; |
| * alias a this; |
| * } |
| * |
| * void fun() |
| * { |
| * A s; |
| * s.remove("foo"); |
| * } |
| */ |
| const errors = global.startGagging(); |
| e = searchUFCS(sc, die, ident); |
| // if there were any errors and the identifier was remove |
| if (global.endGagging(errors)) |
| { |
| if (ident == Id.remove) |
| { |
| // check alias this |
| Expression alias_e = resolveAliasThis(sc, die.e1, 1); |
| if (alias_e && alias_e != die.e1) |
| { |
| die.e1 = alias_e; |
| CallExp ce2 = ce.syntaxCopy(); |
| ce2.e1 = die; |
| e = ce2.isCallExp().trySemantic(sc); |
| if (e) |
| return e; |
| } |
| } |
| // if alias this did not work out, print the initial errors |
| searchUFCS(sc, die, ident); |
| } |
| } |
| else if (auto dti = ce.e1.isDotTemplateInstanceExp()) |
| { |
| if (Expression ey = dti.semanticY(sc, 1)) |
| { |
| ce.e1 = ey; |
| return null; |
| } |
| eleft = dti.e1; |
| e = searchUFCS(sc, dti, dti.ti.name); |
| } |
| else |
| return null; |
| |
| // Rewrite |
| ce.e1 = e; |
| if (!ce.arguments) |
| ce.arguments = new Expressions(); |
| ce.arguments.shift(eleft); |
| |
| return null; |
| } |
| |
| /****************************** |
| * Pull out property with UFCS. |
| */ |
| private Expression resolveUFCSProperties(Scope* sc, Expression e1, Expression e2 = null) |
| { |
| Loc loc = e1.loc; |
| Expression eleft; |
| Expression e; |
| |
| if (auto die = e1.isDotIdExp()) |
| { |
| eleft = die.e1; |
| e = searchUFCS(sc, die, die.ident); |
| } |
| else if (auto dti = e1.isDotTemplateInstanceExp()) |
| { |
| eleft = dti.e1; |
| e = searchUFCS(sc, dti, dti.ti.name); |
| } |
| else |
| return null; |
| |
| if (e is null) |
| return null; |
| |
| // Rewrite |
| if (e2) |
| { |
| // run semantic without gagging |
| e2 = e2.expressionSemantic(sc); |
| |
| /* f(e1) = e2 |
| */ |
| Expression ex = e.copy(); |
| auto a1 = new Expressions(1); |
| (*a1)[0] = eleft; |
| ex = new CallExp(loc, ex, a1); |
| auto e1PassSemantic = ex.trySemantic(sc); |
| |
| /* f(e1, e2) |
| */ |
| auto a2 = new Expressions(2); |
| (*a2)[0] = eleft; |
| (*a2)[1] = e2; |
| e = new CallExp(loc, e, a2); |
| e = e.trySemantic(sc); |
| if (!e1PassSemantic && !e) |
| { |
| /* https://issues.dlang.org/show_bug.cgi?id=20448 |
| * |
| * If both versions have failed to pass semantic, |
| * f(e1) = e2 gets priority in error printing |
| * because f might be a templated function that |
| * failed to instantiate and we have to print |
| * the instantiation errors. |
| */ |
| return e1.expressionSemantic(sc); |
| } |
| else if (ex && !e) |
| { |
| checkPropertyCall(ex); |
| ex = new AssignExp(loc, ex, e2); |
| return ex.expressionSemantic(sc); |
| } |
| else |
| { |
| // strict setter prints errors if fails |
| e = e.expressionSemantic(sc); |
| } |
| checkPropertyCall(e); |
| return e; |
| } |
| else |
| { |
| /* f(e1) |
| */ |
| auto arguments = new Expressions(1); |
| (*arguments)[0] = eleft; |
| e = new CallExp(loc, e, arguments); |
| e = e.expressionSemantic(sc); |
| checkPropertyCall(e); |
| return e.expressionSemantic(sc); |
| } |
| } |
| |
| /****************************** |
| * If e1 is a property function (template), resolve it. |
| */ |
| Expression resolvePropertiesOnly(Scope* sc, Expression e1) |
| { |
| //printf("e1 = %s %s\n", Token.toChars(e1.op), e1.toChars()); |
| |
| Expression handleOverloadSet(OverloadSet os) |
| { |
| assert(os); |
| foreach (s; os.a) |
| { |
| auto fd = s.isFuncDeclaration(); |
| auto td = s.isTemplateDeclaration(); |
| if (fd) |
| { |
| if (fd.type.isTypeFunction().isproperty) |
| return resolveProperties(sc, e1); |
| } |
| else if (td && td.onemember && (fd = td.onemember.isFuncDeclaration()) !is null) |
| { |
| if (fd.type.isTypeFunction().isproperty || |
| (fd.storage_class2 & STC.property) || |
| (td._scope.stc & STC.property)) |
| return resolveProperties(sc, e1); |
| } |
| } |
| return e1; |
| } |
| |
| Expression handleTemplateDecl(TemplateDeclaration td) |
| { |
| assert(td); |
| if (td.onemember) |
| { |
| if (auto fd = td.onemember.isFuncDeclaration()) |
| { |
| if (fd.type.isTypeFunction().isproperty || |
| (fd.storage_class2 & STC.property) || |
| (td._scope.stc & STC.property)) |
| return resolveProperties(sc, e1); |
| } |
| } |
| return e1; |
| } |
| |
| Expression handleFuncDecl(FuncDeclaration fd) |
| { |
| assert(fd); |
| if (fd.type.isTypeFunction().isproperty) |
| return resolveProperties(sc, e1); |
| return e1; |
| } |
| |
| if (auto de = e1.isDotExp()) |
| { |
| if (auto os = de.e2.isOverExp()) |
| return handleOverloadSet(os.vars); |
| } |
| else if (auto oe = e1.isOverExp()) |
| return handleOverloadSet(oe.vars); |
| else if (auto dti = e1.isDotTemplateInstanceExp()) |
| { |
| if (dti.ti.tempdecl) |
| if (auto td = dti.ti.tempdecl.isTemplateDeclaration()) |
| return handleTemplateDecl(td); |
| } |
| else if (auto dte = e1.isDotTemplateExp()) |
| return handleTemplateDecl(dte.td); |
| else if (auto se = e1.isScopeExp()) |
| { |
| Dsymbol s = se.sds; |
| TemplateInstance ti = s.isTemplateInstance(); |
| if (ti && !ti.semanticRun && ti.tempdecl) |
| if (auto td = ti.tempdecl.isTemplateDeclaration()) |
| return handleTemplateDecl(td); |
| } |
| else if (auto et = e1.isTemplateExp()) |
| return handleTemplateDecl(et.td); |
| else if (e1.isDotVarExp() && e1.type.isTypeFunction()) |
| { |
| DotVarExp dve = e1.isDotVarExp(); |
| return handleFuncDecl(dve.var.isFuncDeclaration()); |
| } |
| else if (e1.isVarExp() && e1.type && e1.type.isTypeFunction() && (sc.intypeof || !e1.isVarExp().var.needThis())) |
| return handleFuncDecl(e1.isVarExp().var.isFuncDeclaration()); |
| return e1; |
| } |
| |
| /**************************************** |
| * Turn symbol `s` into the expression it represents. |
| * |
| * Params: |
| * s = symbol to resolve |
| * loc = location of use of `s` |
| * sc = context |
| * hasOverloads = applies if `s` represents a function. |
| * true means it's overloaded and will be resolved later, |
| * false means it's the exact function symbol. |
| * Returns: |
| * `s` turned into an expression, `ErrorExp` if an error occurred |
| */ |
| Expression symbolToExp(Dsymbol s, const ref Loc loc, Scope *sc, bool hasOverloads) |
| { |
| static if (LOGSEMANTIC) |
| { |
| printf("DsymbolExp::resolve(%s %s)\n", s.kind(), s.toChars()); |
| } |
| |
| Lagain: |
| Expression e; |
| |
| //printf("DsymbolExp:: %p '%s' is a symbol\n", this, toChars()); |
| //printf("s = '%s', s.kind = '%s'\n", s.toChars(), s.kind()); |
| Dsymbol olds = s; |
| Declaration d = s.isDeclaration(); |
| if (d && (d.storage_class & STC.templateparameter)) |
| { |
| s = s.toAlias(); |
| } |
| else |
| { |
| // functions are checked after overloading |
| // templates are checked after matching constraints |
| if (!s.isFuncDeclaration() && !s.isTemplateDeclaration()) |
| { |
| s.checkDeprecated(loc, sc); |
| if (d) |
| d.checkDisabled(loc, sc); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12023 |
| // if 's' is a tuple variable, the tuple is returned. |
| s = s.toAlias(); |
| |
| //printf("s = '%s', s.kind = '%s', s.needThis() = %p\n", s.toChars(), s.kind(), s.needThis()); |
| if (s != olds && !s.isFuncDeclaration() && !s.isTemplateDeclaration()) |
| { |
| s.checkDeprecated(loc, sc); |
| if (d) |
| d.checkDisabled(loc, sc); |
| } |
| |
| if (auto sd = s.isDeclaration()) |
| { |
| if (sd.isSystem()) |
| { |
| if (sc.setUnsafePreview(global.params.systemVariables, false, loc, |
| "cannot access `@system` variable `%s` in @safe code", sd)) |
| { |
| return ErrorExp.get(); |
| } |
| } |
| } |
| } |
| |
| if (auto em = s.isEnumMember()) |
| { |
| return em.getVarExp(loc, sc); |
| } |
| if (auto v = s.isVarDeclaration()) |
| { |
| //printf("Identifier '%s' is a variable, type '%s'\n", s.toChars(), v.type.toChars()); |
| if (sc.intypeof == 1 && !v.inuse) |
| v.dsymbolSemantic(sc); |
| if (!v.type || // during variable type inference |
| !v.type.deco && v.inuse) // during variable type semantic |
| { |
| if (v.inuse) // variable type depends on the variable itself |
| error(loc, "circular reference to %s `%s`", v.kind(), v.toPrettyChars()); |
| else // variable type cannot be determined |
| error(loc, "forward reference to %s `%s`", v.kind(), v.toPrettyChars()); |
| return ErrorExp.get(); |
| } |
| if (v.type.ty == Terror) |
| return ErrorExp.get(); |
| |
| if ((v.storage_class & STC.manifest) && v._init) |
| { |
| if (v.inuse) |
| { |
| error(loc, "circular initialization of %s `%s`", v.kind(), v.toPrettyChars()); |
| return ErrorExp.get(); |
| } |
| e = v.expandInitializer(loc); |
| v.inuse++; |
| e = e.expressionSemantic(sc); |
| v.inuse--; |
| return e; |
| } |
| |
| // We need to run semantics to correctly set 'STC.field' if it is a member variable |
| // that could be forward referenced. This is needed for 'v.needThis()' to work |
| if (v.isThis()) |
| v.dsymbolSemantic(sc); |
| |
| // Change the ancestor lambdas to delegate before hasThis(sc) call. |
| if (v.checkNestedReference(sc, loc)) |
| return ErrorExp.get(); |
| |
| if (v.needThis() && hasThis(sc)) |
| e = new DotVarExp(loc, new ThisExp(loc), v); |
| else |
| e = new VarExp(loc, v); |
| e = e.expressionSemantic(sc); |
| return e; |
| } |
| if (auto fld = s.isFuncLiteralDeclaration()) |
| { |
| //printf("'%s' is a function literal\n", fld.toChars()); |
| e = new FuncExp(loc, fld); |
| return e.expressionSemantic(sc); |
| } |
| if (auto f = s.isFuncDeclaration()) |
| { |
| f = f.toAliasFunc(); |
| if (!f.functionSemantic()) |
| return ErrorExp.get(); |
| |
| if (!hasOverloads && f.checkForwardRef(loc)) |
| return ErrorExp.get(); |
| |
| auto fd = s.isFuncDeclaration(); |
| fd.type = f.type; |
| return new VarExp(loc, fd, hasOverloads); |
| } |
| if (OverDeclaration od = s.isOverDeclaration()) |
| { |
| e = new VarExp(loc, od, true); |
| e.type = Type.tvoid; |
| return e; |
| } |
| if (OverloadSet o = s.isOverloadSet()) |
| { |
| //printf("'%s' is an overload set\n", o.toChars()); |
| return new OverExp(loc, o); |
| } |
| |
| if (Import imp = s.isImport()) |
| { |
| if (!imp.pkg) |
| { |
| .error(loc, "forward reference of import `%s`", imp.toChars()); |
| return ErrorExp.get(); |
| } |
| auto ie = new ScopeExp(loc, imp.pkg); |
| return ie.expressionSemantic(sc); |
| } |
| if (Package pkg = s.isPackage()) |
| { |
| auto ie = new ScopeExp(loc, pkg); |
| return ie.expressionSemantic(sc); |
| } |
| if (Module mod = s.isModule()) |
| { |
| auto ie = new ScopeExp(loc, mod); |
| return ie.expressionSemantic(sc); |
| } |
| if (Nspace ns = s.isNspace()) |
| { |
| auto ie = new ScopeExp(loc, ns); |
| return ie.expressionSemantic(sc); |
| } |
| |
| if (Type t = s.getType()) |
| { |
| return (new TypeExp(loc, t)).expressionSemantic(sc); |
| } |
| |
| if (TupleDeclaration tup = s.isTupleDeclaration()) |
| { |
| if (tup.needThis() && hasThis(sc)) |
| e = new DotVarExp(loc, new ThisExp(loc), tup); |
| else |
| e = new TupleExp(loc, tup); |
| e = e.expressionSemantic(sc); |
| return e; |
| } |
| |
| if (TemplateInstance ti = s.isTemplateInstance()) |
| { |
| ti.dsymbolSemantic(sc); |
| if (!ti.inst || ti.errors) |
| return ErrorExp.get(); |
| s = ti.toAlias(); |
| if (!s.isTemplateInstance()) |
| goto Lagain; |
| e = new ScopeExp(loc, ti); |
| e = e.expressionSemantic(sc); |
| return e; |
| } |
| if (TemplateDeclaration td = s.isTemplateDeclaration()) |
| { |
| Dsymbol p = td.toParentLocal(); |
| FuncDeclaration fdthis = hasThis(sc); |
| AggregateDeclaration ad = p ? p.isAggregateDeclaration() : null; |
| if (fdthis && ad && fdthis.isMemberLocal() == ad && (td._scope.stc & STC.static_) == 0) |
| { |
| e = new DotTemplateExp(loc, new ThisExp(loc), td); |
| } |
| else |
| e = new TemplateExp(loc, td); |
| e = e.expressionSemantic(sc); |
| return e; |
| } |
| |
| .error(loc, "%s `%s` is not a variable", s.kind(), s.toChars()); |
| return ErrorExp.get(); |
| } |
| |
| /************************************************************* |
| * Given var, get the |
| * right `this` pointer if var is in an outer class, but our |
| * existing `this` pointer is in an inner class. |
| * Params: |
| * loc = location to use for error messages |
| * sc = context |
| * ad = struct or class we need the correct `this` for |
| * e1 = existing `this` |
| * var = the specific member of ad we're accessing |
| * flag = if true, return `null` instead of throwing an error |
| * Returns: |
| * Expression representing the `this` for the var |
| */ |
| private Expression getRightThis(const ref Loc loc, Scope* sc, AggregateDeclaration ad, Expression e1, Dsymbol var, int flag = 0) |
| { |
| //printf("\ngetRightThis(e1 = %s, ad = %s, var = %s)\n", e1.toChars(), ad.toChars(), var.toChars()); |
| L1: |
| Type t = e1.type.toBasetype(); |
| //printf("e1.type = %s, var.type = %s\n", e1.type.toChars(), var.type.toChars()); |
| |
| if (e1.op == EXP.objcClassReference) |
| { |
| // We already have an Objective-C class reference, just use that as 'this'. |
| return e1; |
| } |
| else if (ad && ad.isClassDeclaration && ad.isClassDeclaration.classKind == ClassKind.objc && |
| var.isFuncDeclaration && var.isFuncDeclaration.isStatic && |
| var.isFuncDeclaration.objc.selector) |
| { |
| return new ObjcClassReferenceExp(e1.loc, ad.isClassDeclaration()); |
| } |
| |
| /* Access of a member which is a template parameter in dual-scope scenario |
| * class A { inc(alias m)() { ++m; } } // `m` needs `this` of `B` |
| * class B {int m; inc() { new A().inc!m(); } } |
| */ |
| if (e1.op == EXP.this_) |
| { |
| FuncDeclaration f = hasThis(sc); |
| if (f && f.hasDualContext()) |
| { |
| if (f.followInstantiationContext(ad)) |
| { |
| e1 = new VarExp(loc, f.vthis); |
| e1 = new PtrExp(loc, e1); |
| e1 = new IndexExp(loc, e1, IntegerExp.literal!1); |
| e1 = getThisSkipNestedFuncs(loc, sc, f.toParent2(), ad, e1, t, var); |
| if (e1.op == EXP.error) |
| return e1; |
| goto L1; |
| } |
| } |
| } |
| |
| /* If e1 is not the 'this' pointer for ad |
| */ |
| if (ad && |
| !(t.isTypePointer() && t.nextOf().isTypeStruct() && t.nextOf().isTypeStruct().sym == ad) && |
| !(t.isTypeStruct() && t.isTypeStruct().sym == ad)) |
| { |
| ClassDeclaration cd = ad.isClassDeclaration(); |
| ClassDeclaration tcd = t.isClassHandle(); |
| |
| /* e1 is the right this if ad is a base class of e1 |
| */ |
| if (!cd || !tcd || !(tcd == cd || cd.isBaseOf(tcd, null))) |
| { |
| /* Only classes can be inner classes with an 'outer' |
| * member pointing to the enclosing class instance |
| */ |
| if (tcd && tcd.isNested()) |
| { |
| /* e1 is the 'this' pointer for an inner class: tcd. |
| * Rewrite it as the 'this' pointer for the outer class. |
| */ |
| auto vthis = tcd.followInstantiationContext(ad) ? tcd.vthis2 : tcd.vthis; |
| e1 = new DotVarExp(loc, e1, vthis); |
| e1.type = vthis.type; |
| e1.type = e1.type.addMod(t.mod); |
| // Do not call ensureStaticLinkTo() |
| //e1 = e1.semantic(sc); |
| |
| // Skip up over nested functions, and get the enclosing |
| // class type. |
| e1 = getThisSkipNestedFuncs(loc, sc, tcd.toParentP(ad), ad, e1, t, var); |
| if (e1.op == EXP.error) |
| return e1; |
| goto L1; |
| } |
| |
| /* Can't find a path from e1 to ad |
| */ |
| if (flag) |
| return null; |
| e1.error("`this` for `%s` needs to be type `%s` not type `%s`", var.toChars(), ad.toChars(), t.toChars()); |
| return ErrorExp.get(); |
| } |
| } |
| return e1; |
| } |
| |
| /*************************************** |
| * Pull out any properties. |
| */ |
| private Expression resolvePropertiesX(Scope* sc, Expression e1, Expression e2 = null) |
| { |
| //printf("resolvePropertiesX, e1 = %s %s, e2 = %s\n", EXPtoString(e1.op).ptr, e1.toChars(), e2 ? e2.toChars() : null); |
| Loc loc = e1.loc; |
| |
| OverloadSet os; |
| Dsymbol s; |
| Objects* tiargs; |
| Type tthis; |
| if (auto de = e1.isDotExp()) |
| { |
| if (auto oe = de.e2.isOverExp()) |
| { |
| tiargs = null; |
| tthis = de.e1.type; |
| os = oe.vars; |
| goto Los; |
| } |
| } |
| else if (e1.isOverExp()) |
| { |
| tiargs = null; |
| tthis = null; |
| os = e1.isOverExp().vars; |
| Los: |
| assert(os); |
| FuncDeclaration fd = null; |
| if (e2) |
| { |
| e2 = e2.expressionSemantic(sc); |
| if (e2.op == EXP.error) |
| return ErrorExp.get(); |
| e2 = resolveProperties(sc, e2); |
| |
| Expressions a; |
| a.push(e2); |
| |
| for (size_t i = 0; i < os.a.dim; i++) |
| { |
| if (FuncDeclaration f = resolveFuncCall(loc, sc, os.a[i], tiargs, tthis, &a, FuncResolveFlag.quiet)) |
| { |
| if (f.errors) |
| return ErrorExp.get(); |
| fd = f; |
| assert(fd.type.ty == Tfunction); |
| } |
| } |
| if (fd) |
| { |
| Expression e = new CallExp(loc, e1, e2); |
| return e.expressionSemantic(sc); |
| } |
| } |
| { |
| for (size_t i = 0; i < os.a.dim; i++) |
| { |
| if (FuncDeclaration f = resolveFuncCall(loc, sc, os.a[i], tiargs, tthis, null, FuncResolveFlag.quiet)) |
| { |
| if (f.errors) |
| return ErrorExp.get(); |
| fd = f; |
| assert(fd.type.ty == Tfunction); |
| auto tf = fd.type.isTypeFunction(); |
| if (!tf.isref && e2) |
| { |
| error(loc, "%s is not an lvalue", e1.toChars()); |
| return ErrorExp.get(); |
| } |
| } |
| } |
| if (fd) |
| { |
| Expression e = new CallExp(loc, e1); |
| if (e2) |
| e = new AssignExp(loc, e, e2); |
| return e.expressionSemantic(sc); |
| } |
| } |
| if (e2) |
| goto Leprop; |
| } |
| else if (auto dti = e1.isDotTemplateInstanceExp()) |
| { |
| if (!dti.findTempDecl(sc)) |
| goto Leprop; |
| if (!dti.ti.semanticTiargs(sc)) |
| goto Leprop; |
| tiargs = dti.ti.tiargs; |
| tthis = dti.e1.type; |
| if ((os = dti.ti.tempdecl.isOverloadSet()) !is null) |
| goto Los; |
| if ((s = dti.ti.tempdecl) !is null) |
| goto Lfd; |
| } |
| else if (auto dte = e1.isDotTemplateExp()) |
| { |
| s = dte.td; |
| tiargs = null; |
| tthis = dte.e1.type; |
| goto Lfd; |
| } |
| else if (auto se = e1.isScopeExp()) |
| { |
| s = se.sds; |
| TemplateInstance ti = s.isTemplateInstance(); |
| if (ti && !ti.semanticRun && ti.tempdecl) |
| { |
| //assert(ti.needsTypeInference(sc)); |
| if (!ti.semanticTiargs(sc)) |
| goto Leprop; |
| tiargs = ti.tiargs; |
| tthis = null; |
| if ((os = ti.tempdecl.isOverloadSet()) !is null) |
| goto Los; |
| if ((s = ti.tempdecl) !is null) |
| goto Lfd; |
| } |
| } |
| else if (auto te = e1.isTemplateExp()) |
| { |
| s = te.td; |
| tiargs = null; |
| tthis = null; |
| goto Lfd; |
| } |
| else if (e1.isDotVarExp() && e1.type && (e1.type.toBasetype().isTypeFunction() || e1.isDotVarExp().var.isOverDeclaration())) |
| { |
| DotVarExp dve = e1.isDotVarExp(); |
| s = dve.var; |
| tiargs = null; |
| tthis = dve.e1.type; |
| goto Lfd; |
| } |
| else if (sc && sc.flags & SCOPE.Cfile && e1.isVarExp() && !e2) |
| { |
| // ImportC: do not implicitly call function if no ( ) are present |
| } |
| else if (e1.isVarExp() && e1.type && (e1.type.toBasetype().isTypeFunction() || e1.isVarExp().var.isOverDeclaration())) |
| { |
| s = e1.isVarExp().var; |
| tiargs = null; |
| tthis = null; |
| Lfd: |
| assert(s); |
| if (e2) |
| { |
| e2 = e2.expressionSemantic(sc); |
| if (e2.op == EXP.error) |
| return ErrorExp.get(); |
| e2 = resolveProperties(sc, e2); |
| |
| Expressions a; |
| a.push(e2); |
| |
| FuncDeclaration fd = resolveFuncCall(loc, sc, s, tiargs, tthis, &a, FuncResolveFlag.quiet); |
| if (fd && fd.type) |
| { |
| if (fd.errors) |
| return ErrorExp.get(); |
| if (!checkSymbolAccess(sc, fd)) |
| { |
| // @@@DEPRECATED_2.105@@@ |
| // When turning into error, uncomment the return statement |
| TypeFunction tf = fd.type.isTypeFunction(); |
| deprecation(loc, "function `%s` of type `%s` is not accessible from module `%s`", |
| fd.toPrettyChars(), tf.toChars, sc._module.toChars); |
| //return ErrorExp.get(); |
| } |
| assert(fd.type.ty == Tfunction); |
| Expression e = new CallExp(loc, e1, e2); |
| return e.expressionSemantic(sc); |
| } |
| } |
| { |
| FuncDeclaration fd = resolveFuncCall(loc, sc, s, tiargs, tthis, null, FuncResolveFlag.quiet); |
| if (fd && fd.type) |
| { |
| if (fd.errors) |
| return ErrorExp.get(); |
| TypeFunction tf = fd.type.isTypeFunction(); |
| if (!e2 || tf.isref) |
| { |
| if (!checkSymbolAccess(sc, fd)) |
| { |
| // @@@DEPRECATED_2.105@@@ |
| // When turning into error, uncomment the return statement |
| deprecation(loc, "function `%s` of type `%s` is not accessible from module `%s`", |
| fd.toPrettyChars(), tf.toChars, sc._module.toChars); |
| //return ErrorExp.get(); |
| } |
| Expression e = new CallExp(loc, e1); |
| if (e2) |
| e = new AssignExp(loc, e, e2); |
| return e.expressionSemantic(sc); |
| } |
| } |
| } |
| if (FuncDeclaration fd = s.isFuncDeclaration()) |
| { |
| // Keep better diagnostic message for invalid property usage of functions |
| assert(fd.type.ty == Tfunction); |
| Expression e = new CallExp(loc, e1, e2); |
| return e.expressionSemantic(sc); |
| } |
| if (e2) |
| goto Leprop; |
| } |
| if (auto ve = e1.isVarExp()) |
| { |
| if (auto v = ve.var.isVarDeclaration()) |
| { |
| if (ve.checkPurity(sc, v)) |
| return ErrorExp.get(); |
| } |
| } |
| if (e2) |
| return null; |
| |
| if (e1.type && !e1.isTypeExp()) // function type is not a property |
| { |
| /* Look for e1 being a lazy parameter; rewrite as delegate call |
| * only if the symbol wasn't already treated as a delegate |
| */ |
| auto ve = e1.isVarExp(); |
| if (ve && ve.var.storage_class & STC.lazy_ && !ve.delegateWasExtracted) |
| { |
| Expression e = new CallExp(loc, e1); |
| return e.expressionSemantic(sc); |
| } |
| else if (e1.isDotVarExp()) |
| { |
| // Check for reading overlapped pointer field in @safe code. |
| if (checkUnsafeAccess(sc, e1, true, true)) |
| return ErrorExp.get(); |
| } |
| else if (auto ce = e1.isCallExp()) |
| { |
| // Check for reading overlapped pointer field in @safe code. |
| if (checkUnsafeAccess(sc, ce.e1, true, true)) |
| return ErrorExp.get(); |
| } |
| } |
| |
| if (!e1.type) |
| { |
| error(loc, "cannot resolve type for %s", e1.toChars()); |
| e1 = ErrorExp.get(); |
| } |
| return e1; |
| |
| Leprop: |
| error(loc, "not a property %s", e1.toChars()); |
| return ErrorExp.get(); |
| } |
| |
| extern (C++) Expression resolveProperties(Scope* sc, Expression e) |
| { |
| //printf("resolveProperties(%s)\n", e.toChars()); |
| e = resolvePropertiesX(sc, e); |
| if (e.checkRightThis(sc)) |
| return ErrorExp.get(); |
| return e; |
| } |
| |
| /**************************************** |
| * The common type is determined by applying ?: to each pair. |
| * Output: |
| * exps[] properties resolved, implicitly cast to common type, rewritten in place |
| * Returns: |
| * The common type, or `null` if an error has occured |
| */ |
| private Type arrayExpressionToCommonType(Scope* sc, ref Expressions exps) |
| { |
| /* Still have a problem with: |
| * ubyte[][] = [ cast(ubyte[])"hello", [1]]; |
| * which works if the array literal is initialized top down with the ubyte[][] |
| * type, but fails with this function doing bottom up typing. |
| */ |
| |
| //printf("arrayExpressionToCommonType()\n"); |
| scope IntegerExp integerexp = IntegerExp.literal!0; |
| scope CondExp condexp = new CondExp(Loc.initial, integerexp, null, null); |
| |
| Type t0 = null; |
| Expression e0 = null; |
| bool foundType; |
| |
| for (size_t i = 0; i < exps.dim; i++) |
| { |
| Expression e = exps[i]; |
| if (!e) |
| continue; |
| |
| e = resolveProperties(sc, e); |
| if (!e.type) |
| { |
| e.error("`%s` has no value", e.toChars()); |
| t0 = Type.terror; |
| continue; |
| } |
| if (e.op == EXP.type) |
| { |
| foundType = true; // do not break immediately, there might be more errors |
| e.checkValue(); // report an error "type T has no value" |
| t0 = Type.terror; |
| continue; |
| } |
| if (e.type.ty == Tvoid) |
| { |
| // void expressions do not concur to the determination of the common |
| // type. |
| continue; |
| } |
| if (checkNonAssignmentArrayOp(e)) |
| { |
| t0 = Type.terror; |
| continue; |
| } |
| |
| e = doCopyOrMove(sc, e); |
| |
| if (!foundType && t0 && !t0.equals(e.type)) |
| { |
| /* This applies ?: to merge the types. It's backwards; |
| * ?: should call this function to merge types. |
| */ |
| condexp.type = null; |
| condexp.e1 = e0; |
| condexp.e2 = e; |
| condexp.loc = e.loc; |
| Expression ex = condexp.expressionSemantic(sc); |
| if (ex.op == EXP.error) |
| e = ex; |
| else |
| { |
| // Convert to common type |
| exps[i] = condexp.e1.castTo(sc, condexp.type); |
| e = condexp.e2.castTo(sc, condexp.type); |
| } |
| } |
| e0 = e; |
| t0 = e.type; |
| if (e.op != EXP.error) |
| exps[i] = e; |
| } |
| |
| // [] is typed as void[] |
| if (!t0) |
| return Type.tvoid; |
| |
| // It's an error, don't do the cast |
| if (t0.ty == Terror) |
| return null; |
| |
| for (size_t i = 0; i < exps.dim; i++) |
| { |
| Expression e = exps[i]; |
| if (!e) |
| continue; |
| |
| e = e.implicitCastTo(sc, t0); |
| if (e.op == EXP.error) |
| { |
| /* https://issues.dlang.org/show_bug.cgi?id=13024 |
| * a workaround for the bug in typeMerge - |
| * it should paint e1 and e2 by deduced common type, |
| * but doesn't in this particular case. |
| */ |
| return null; |
| } |
| exps[i] = e; |
| } |
| return t0; |
| } |
| |
| private Expression opAssignToOp(const ref Loc loc, EXP op, Expression e1, Expression e2) |
| { |
| Expression e; |
| switch (op) |
| { |
| case EXP.addAssign: |
| e = new AddExp(loc, e1, e2); |
| break; |
| |
| case EXP.minAssign: |
| e = new MinExp(loc, e1, e2); |
| break; |
| |
| case EXP.mulAssign: |
| e = new MulExp(loc, e1, e2); |
| break; |
| |
| case EXP.divAssign: |
| e = new DivExp(loc, e1, e2); |
| break; |
| |
| case EXP.modAssign: |
| e = new ModExp(loc, e1, e2); |
| break; |
| |
| case EXP.andAssign: |
| e = new AndExp(loc, e1, e2); |
| break; |
| |
| case EXP.orAssign: |
| e = new OrExp(loc, e1, e2); |
| break; |
| |
| case EXP.xorAssign: |
| e = new XorExp(loc, e1, e2); |
| break; |
| |
| case EXP.leftShiftAssign: |
| e = new ShlExp(loc, e1, e2); |
| break; |
| |
| case EXP.rightShiftAssign: |
| e = new ShrExp(loc, e1, e2); |
| break; |
| |
| case EXP.unsignedRightShiftAssign: |
| e = new UshrExp(loc, e1, e2); |
| break; |
| |
| default: |
| assert(0); |
| } |
| return e; |
| } |
| |
| /********************* |
| * Rewrite: |
| * array.length op= e2 |
| * as: |
| * array.length = array.length op e2 |
| * or: |
| * auto tmp = &array; |
| * (*tmp).length = (*tmp).length op e2 |
| */ |
| private Expression rewriteOpAssign(BinExp exp) |
| { |
| ArrayLengthExp ale = exp.e1.isArrayLengthExp(); |
| if (ale.e1.isVarExp()) |
| { |
| Expression e = opAssignToOp(exp.loc, exp.op, ale, exp.e2); |
| e = new AssignExp(exp.loc, ale.syntaxCopy(), e); |
| return e; |
| } |
| else |
| { |
| /* auto tmp = &array; |
| * (*tmp).length = (*tmp).length op e2 |
| */ |
| auto tmp = copyToTemp(0, "__arraylength", new AddrExp(ale.loc, ale.e1)); |
| |
| Expression e1 = new ArrayLengthExp(ale.loc, new PtrExp(ale.loc, new VarExp(ale.loc, tmp))); |
| Expression elvalue = e1.syntaxCopy(); |
| Expression e = opAssignToOp(exp.loc, exp.op, e1, exp.e2); |
| e = new AssignExp(exp.loc, elvalue, e); |
| e = new CommaExp(exp.loc, new DeclarationExp(ale.loc, tmp), e); |
| return e; |
| } |
| } |
| |
| /**************************************** |
| * Preprocess arguments to function. |
| * Input: |
| * reportErrors whether or not to report errors here. Some callers are not |
| * checking actual function params, so they'll do their own error reporting |
| * Output: |
| * exps[] tuples expanded, properties resolved, rewritten in place |
| * Returns: |
| * true a semantic error occurred |
| */ |
| private bool preFunctionParameters(Scope* sc, Expressions* exps, const bool reportErrors = true) |
| { |
| bool err = false; |
| if (exps) |
| { |
| expandTuples(exps); |
| |
| for (size_t i = 0; i < exps.dim; i++) |
| { |
| Expression arg = (*exps)[i]; |
| arg = resolveProperties(sc, arg); |
| arg = arg.arrayFuncConv(sc); |
| if (arg.op == EXP.type) |
| { |
| // for static alias this: https://issues.dlang.org/show_bug.cgi?id=17684 |
| arg = resolveAliasThis(sc, arg); |
| |
| if (arg.op == EXP.type) |
| { |
| if (reportErrors) |
| { |
| arg.error("cannot pass type `%s` as a function argument", arg.toChars()); |
| arg = ErrorExp.get(); |
| } |
| err = true; |
| } |
| } |
| else if (arg.type.toBasetype().ty == Tfunction) |
| { |
| if (reportErrors) |
| { |
| arg.error("cannot pass function `%s` as a function argument", arg.toChars()); |
| arg = ErrorExp.get(); |
| } |
| err = true; |
| } |
| else if (checkNonAssignmentArrayOp(arg)) |
| { |
| arg = ErrorExp.get(); |
| err = true; |
| } |
| (*exps)[i] = arg; |
| } |
| } |
| return err; |
| } |
| |
| /******************************************** |
| * Issue an error if default construction is disabled for type t. |
| * Default construction is required for arrays and 'out' parameters. |
| * Returns: |
| * true an error was issued |
| */ |
| private bool checkDefCtor(Loc loc, Type t) |
| { |
| if (auto ts = t.baseElemOf().isTypeStruct()) |
| { |
| StructDeclaration sd = ts.sym; |
| if (sd.noDefaultCtor) |
| { |
| sd.error(loc, "default construction is disabled"); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Now that we know the exact type of the function we're calling, |
| * the arguments[] need to be adjusted: |
| * 1. implicitly convert argument to the corresponding parameter type |
| * 2. add default arguments for any missing arguments |
| * 3. do default promotions on arguments corresponding to ... |
| * 4. add hidden _arguments[] argument |
| * 5. call copy constructor for struct value arguments |
| * Params: |
| * loc = location of function call |
| * sc = context |
| * tf = type of the function |
| * ethis = `this` argument, `null` if none or not known |
| * tthis = type of `this` argument, `null` if no `this` argument |
| * arguments = array of actual arguments to function call |
| * fd = the function being called, `null` if called indirectly |
| * prettype = set to return type of function |
| * peprefix = set to expression to execute before `arguments[]` are evaluated, `null` if none |
| * Returns: |
| * true errors happened |
| */ |
| private bool functionParameters(const ref Loc loc, Scope* sc, |
| TypeFunction tf, Expression ethis, Type tthis, Expressions* arguments, FuncDeclaration fd, |
| Type* prettype, Expression* peprefix) |
| { |
| //printf("functionParameters() %s\n", fd ? fd.toChars() : ""); |
| assert(arguments); |
| assert(fd || tf.next); |
| size_t nargs = arguments ? arguments.dim : 0; |
| const size_t nparams = tf.parameterList.length; |
| const olderrors = global.errors; |
| bool err = false; |
| *prettype = Type.terror; |
| Expression eprefix = null; |
| *peprefix = null; |
| |
| if (nargs > nparams && tf.parameterList.varargs == VarArg.none) |
| { |
| error(loc, "expected %llu arguments, not %llu for non-variadic function type `%s`", cast(ulong)nparams, cast(ulong)nargs, tf.toChars()); |
| return true; |
| } |
| |
| // If inferring return type, and semantic3() needs to be run if not already run |
| if (!tf.next && fd.inferRetType) |
| { |
| fd.functionSemantic(); |
| } |
| else if (fd && fd.parent) |
| { |
| TemplateInstance ti = fd.parent.isTemplateInstance(); |
| if (ti && ti.tempdecl) |
| { |
| fd.functionSemantic3(); |
| } |
| } |
| |
| /* If calling a pragma(inline, true) function, |
| * set flag to later scan for inlines. |
| */ |
| if (fd && fd.inlining == PINLINE.always) |
| { |
| if (sc._module) |
| sc._module.hasAlwaysInlines = true; |
| if (sc.func) |
| sc.func.hasAlwaysInlines = true; |
| } |
| |
| const isCtorCall = fd && fd.needThis() && fd.isCtorDeclaration(); |
| |
| const size_t n = (nargs > nparams) ? nargs : nparams; // n = max(nargs, nparams) |
| |
| /* If the function return type has wildcards in it, we'll need to figure out the actual type |
| * based on the actual argument types. |
| * Start with the `this` argument, later on merge into wildmatch the mod bits of the rest |
| * of the arguments. |
| */ |
| MOD wildmatch = (tthis && !isCtorCall) ? tthis.Type.deduceWild(tf, false) : 0; |
| |
| bool done = false; |
| foreach (const i; 0 .. n) |
| { |
| Expression arg = (i < nargs) ? (*arguments)[i] : null; |
| |
| if (i < nparams) |
| { |
| bool errorArgs() |
| { |
| error(loc, "expected %llu function arguments, not %llu", cast(ulong)nparams, cast(ulong)nargs); |
| return true; |
| } |
| |
| Parameter p = tf.parameterList[i]; |
| |
| if (!arg) |
| { |
| if (!p.defaultArg) |
| { |
| if (tf.parameterList.varargs == VarArg.typesafe && i + 1 == nparams) |
| goto L2; |
| return errorArgs(); |
| } |
| arg = p.defaultArg; |
| arg = inlineCopy(arg, sc); |
| // __FILE__, __LINE__, __MODULE__, __FUNCTION__, and __PRETTY_FUNCTION__ |
| arg = arg.resolveLoc(loc, sc); |
| arguments.push(arg); |
| nargs++; |
| } |
| else |
| { |
| if (isDefaultInitOp(arg.op)) |
| { |
| arg = arg.resolveLoc(loc, sc); |
| (*arguments)[i] = arg; |
| } |
| } |
| |
| |
| if (tf.parameterList.varargs == VarArg.typesafe && i + 1 == nparams) // https://dlang.org/spec/function.html#variadic |
| { |
| //printf("\t\tvarargs == 2, p.type = '%s'\n", p.type.toChars()); |
| { |
| MATCH m; |
| if ((m = arg.implicitConvTo(p.type)) > MATCH.nomatch) |
| { |
| if (p.type.nextOf() && arg.implicitConvTo(p.type.nextOf()) >= m) |
| goto L2; |
| else if (nargs != nparams) |
| return errorArgs(); |
| goto L1; |
| } |
| } |
| L2: |
| Type tb = p.type.toBasetype(); |
| switch (tb.ty) |
| { |
| case Tsarray: |
| case Tarray: |
| { |
| /* Create a static array variable v of type arg.type: |
| * T[dim] __arrayArg = [ arguments[i], ..., arguments[nargs-1] ]; |
| * |
| * The array literal in the initializer of the hidden variable |
| * is now optimized. |
| * https://issues.dlang.org/show_bug.cgi?id=2356 |
| */ |
| Type tbn = (cast(TypeArray)tb).next; // array element type |
| Type tret = p.isLazyArray(); |
| |
| auto elements = new Expressions(nargs - i); |
| foreach (u; 0 .. elements.dim) |
| { |
| Expression a = (*arguments)[i + u]; |
| if (tret && a.implicitConvTo(tret)) |
| { |
| // p is a lazy array of delegates, tret is return type of the delegates |
| a = a.implicitCastTo(sc, tret) |
| .optimize(WANTvalue) |
| .toDelegate(tret, sc); |
| } |
| else |
| a = a.implicitCastTo(sc, tbn); |
| a = a.addDtorHook(sc); |
| (*elements)[u] = a; |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=14395 |
| // Convert to a static array literal, or its slice. |
| arg = new ArrayLiteralExp(loc, tbn.sarrayOf(nargs - i), elements); |
| if (tb.ty == Tarray) |
| { |
| arg = new SliceExp(loc, arg, null, null); |
| arg.type = p.type; |
| } |
| break; |
| } |
| case Tclass: |
| { |
| /* Set arg to be: |
| * new Tclass(arg0, arg1, ..., argn) |
| */ |
| auto args = new Expressions(nargs - i); |
| foreach (u; i .. nargs) |
| (*args)[u - i] = (*arguments)[u]; |
| arg = new NewExp(loc, null, p.type, args); |
| break; |
| } |
| default: |
| if (!arg) |
| { |
| error(loc, "not enough arguments"); |
| return true; |
| } |
| break; |
| } |
| arg = arg.expressionSemantic(sc); |
| //printf("\targ = '%s'\n", arg.toChars()); |
| arguments.setDim(i + 1); |
| (*arguments)[i] = arg; |
| nargs = i + 1; |
| done = true; |
| } |
| |
| L1: |
| if (!(p.isLazy() && p.type.ty == Tvoid)) |
| { |
| if (ubyte wm = arg.type.deduceWild(p.type, p.isReference())) |
| { |
| wildmatch = wildmatch ? MODmerge(wildmatch, wm) : wm; |
| //printf("[%d] p = %s, a = %s, wm = %d, wildmatch = %d\n", i, p.type.toChars(), arg.type.toChars(), wm, wildmatch); |
| } |
| } |
| } |
| if (done) |
| break; |
| } |
| if ((wildmatch == MODFlags.mutable || wildmatch == MODFlags.immutable_) && |
| tf.next && tf.next.hasWild() && |
| (tf.isref || !tf.next.implicitConvTo(tf.next.immutableOf()))) |
| { |
| bool errorInout(MOD wildmatch) |
| { |
| const(char)* s = wildmatch == MODFlags.mutable ? "mutable" : MODtoChars(wildmatch); |
| error(loc, "modify `inout` to `%s` is not allowed inside `inout` function", s); |
| return true; |
| } |
| |
| if (fd) |
| { |
| /* If the called function may return the reference to |
| * outer inout data, it should be rejected. |
| * |
| * void foo(ref inout(int) x) { |
| * ref inout(int) bar(inout(int)) { return x; } |
| * struct S { |
| * ref inout(int) bar() inout { return x; } |
| * ref inout(int) baz(alias a)() inout { return x; } |
| * } |
| * bar(int.init) = 1; // bad! |
| * S().bar() = 1; // bad! |
| * } |
| * void test() { |
| * int a; |
| * auto s = foo(a); |
| * s.baz!a() = 1; // bad! |
| * } |
| * |
| */ |
| bool checkEnclosingWild(Dsymbol s) |
| { |
| bool checkWild(Dsymbol s) |
| { |
| if (!s) |
| return false; |
| if (auto ad = s.isAggregateDeclaration()) |
| { |
| if (ad.isNested()) |
| return checkEnclosingWild(s); |
| } |
| else if (auto ff = s.isFuncDeclaration()) |
| { |
| if (ff.type.isTypeFunction().iswild) |
| return errorInout(wildmatch); |
| |
| if (ff.isNested() || ff.isThis()) |
| return checkEnclosingWild(s); |
| } |
| return false; |
| } |
| |
| Dsymbol ctx0 = s.toParent2(); |
| Dsymbol ctx1 = s.toParentLocal(); |
| if (checkWild(ctx0)) |
| return true; |
| if (ctx0 != ctx1) |
| return checkWild(ctx1); |
| return false; |
| } |
| if ((fd.isThis() || fd.isNested()) && checkEnclosingWild(fd)) |
| return true; |
| } |
| else if (tf.isWild()) |
| return errorInout(wildmatch); |
| } |
| |
| Expression firstArg = ((tf.next && tf.next.ty == Tvoid || isCtorCall) && |
| tthis && |
| tthis.isMutable() && tthis.toBasetype().ty == Tstruct && |
| tthis.hasPointers()) |
| ? ethis : null; |
| |
| assert(nargs >= nparams); |
| foreach (const i, arg; (*arguments)[0 .. nargs]) |
| { |
| assert(arg); |
| if (i < nparams) |
| { |
| Parameter p = tf.parameterList[i]; |
| Type targ = arg.type; // keep original type for isCopyable() because alias this |
| // resolution may hide an uncopyable type |
| |
| if (!(p.isLazy() && p.type.ty == Tvoid)) |
| { |
| Type tprm = p.type.hasWild() |
| ? p.type.substWildTo(wildmatch) |
| : p.type; |
| |
| const hasCopyCtor = arg.type.isTypeStruct() && arg.type.isTypeStruct().sym.hasCopyCtor; |
| const typesMatch = arg.type.mutableOf().unSharedOf().equals(tprm.mutableOf().unSharedOf()); |
| if (!((hasCopyCtor && typesMatch) || tprm.equals(arg.type))) |
| { |
| //printf("arg.type = %s, p.type = %s\n", arg.type.toChars(), p.type.toChars()); |
| arg = arg.implicitCastTo(sc, tprm); |
| arg = arg.optimize(WANTvalue, p.isReference()); |
| } |
| } |
| |
| // Support passing rvalue to `in` parameters |
| if ((p.storageClass & (STC.in_ | STC.ref_)) == (STC.in_ | STC.ref_)) |
| { |
| if (!arg.isLvalue()) |
| { |
| auto v = copyToTemp(STC.exptemp, "__rvalue", arg); |
| Expression ev = new DeclarationExp(arg.loc, v); |
| ev = new CommaExp(arg.loc, ev, new VarExp(arg.loc, v)); |
| arg = ev.expressionSemantic(sc); |
| } |
| arg = arg.toLvalue(sc, arg); |
| |
| // Look for mutable misaligned pointer, etc., in @safe mode |
| err |= checkUnsafeAccess(sc, arg, false, true); |
| } |
| else if (p.storageClass & STC.ref_) |
| { |
| if (global.params.rvalueRefParam == FeatureState.enabled && |
| !arg.isLvalue() && |
| targ.isCopyable()) |
| { /* allow rvalues to be passed to ref parameters by copying |
| * them to a temp, then pass the temp as the argument |
| */ |
| auto v = copyToTemp(0, "__rvalue", arg); |
| Expression ev = new DeclarationExp(arg.loc, v); |
| ev = new CommaExp(arg.loc, ev, new VarExp(arg.loc, v)); |
| arg = ev.expressionSemantic(sc); |
| } |
| arg = arg.toLvalue(sc, arg); |
| |
| // Look for mutable misaligned pointer, etc., in @safe mode |
| err |= checkUnsafeAccess(sc, arg, false, true); |
| } |
| else if (p.storageClass & STC.out_) |
| { |
| Type t = arg.type; |
| if (!t.isMutable() || !t.isAssignable()) // check blit assignable |
| { |
| arg.error("cannot modify struct `%s` with immutable members", arg.toChars()); |
| err = true; |
| } |
| else |
| { |
| // Look for misaligned pointer, etc., in @safe mode |
| err |= checkUnsafeAccess(sc, arg, false, true); |
| err |= checkDefCtor(arg.loc, t); // t must be default constructible |
| } |
| arg = arg.toLvalue(sc, arg); |
| } |
| else if (p.isLazy()) |
| { |
| // Convert lazy argument to a delegate |
| auto t = (p.type.ty == Tvoid) ? p.type : arg.type; |
| arg = toDelegate(arg, t, sc); |
| } |
| //printf("arg: %s\n", arg.toChars()); |
| //printf("type: %s\n", arg.type.toChars()); |
| //printf("param: %s\n", p.toChars()); |
| |
| const pStc = tf.parameterStorageClass(tthis, p); |
| |
| if (firstArg && (pStc & STC.return_)) |
| { |
| /* Argument value can be assigned to firstArg. |
| * Check arg to see if it matters. |
| */ |
| err |= checkParamArgumentReturn(sc, firstArg, arg, p, false); |
| } |
| // Allow 'lazy' to imply 'scope' - lazy parameters can be passed along |
| // as lazy parameters to the next function, but that isn't escaping. |
| else if (!(pStc & STC.lazy_)) |
| { |
| /* Argument value can escape from the called function. |
| * Check arg to see if it matters. |
| */ |
| VarDeclaration vPar = fd ? (fd.parameters ? (*fd.parameters)[i] : null) : null; |
| err |= checkParamArgumentEscape(sc, fd, p, vPar, cast(STC) pStc, arg, false, false); |
| } |
| |
| // Turning heap allocations into stack allocations is dangerous without dip1000, since `scope` inference |
| // may be unreliable when scope violations only manifest as deprecation warnings. |
| // However, existing `@nogc` code may rely on it, so still do it when the parameter is explicitly marked `scope` |
| const explicitScope = p.isLazy() || |
| ((p.storageClass & STC.scope_) && !(p.storageClass & STC.scopeinferred)); |
| if ((pStc & (STC.scope_ | STC.lazy_)) && |
| ((global.params.useDIP1000 == FeatureState.enabled) || explicitScope) && |
| !(pStc & STC.return_)) |
| { |
| /* Argument value cannot escape from the called function. |
| */ |
| Expression a = arg; |
| if (auto ce = a.isCastExp()) |
| a = ce.e1; |
| |
| ArrayLiteralExp ale; |
| if (p.type.toBasetype().ty == Tarray && |
| (ale = a.isArrayLiteralExp()) !is null && ale.elements && ale.elements.length > 0) |
| { |
| // allocate the array literal as temporary static array on the stack |
| ale.type = ale.type.nextOf().sarrayOf(ale.elements.length); |
| auto tmp = copyToTemp(0, "__arrayliteral_on_stack", ale); |
| auto declareTmp = new DeclarationExp(ale.loc, tmp); |
| auto castToSlice = new CastExp(ale.loc, new VarExp(ale.loc, tmp), |
| p.type.substWildTo(MODFlags.mutable)); |
| arg = CommaExp.combine(declareTmp, castToSlice); |
| arg = arg.expressionSemantic(sc); |
| } |
| else if (auto fe = a.isFuncExp()) |
| { |
| /* Function literals can only appear once, so if this |
| * appearance was scoped, there cannot be any others. |
| */ |
| fe.fd.tookAddressOf = 0; |
| } |
| else if (auto de = a.isDelegateExp()) |
| { |
| /* For passing a delegate to a scoped parameter, |
| * this doesn't count as taking the address of it. |
| * We only worry about 'escaping' references to the function. |
| */ |
| if (auto ve = de.e1.isVarExp()) |
| { |
| if (auto f = ve.var.isFuncDeclaration()) |
| { |
| if (f.tookAddressOf) |
| --f.tookAddressOf; |
| //printf("--tookAddressOf = %d\n", f.tookAddressOf); |
| } |
| } |
| } |
| } |
| if (!p.isReference()) |
| err |= arg.checkSharedAccess(sc); |
| |
| arg = arg.optimize(WANTvalue, p.isReference()); |
| |
| /* Determine if this parameter is the "first reference" parameter through which |
| * later "return" arguments can be stored. |
| */ |
| if (i == 0 && !tthis && p.isReference() && p.type && |
| (tf.next && tf.next.ty == Tvoid || isCtorCall)) |
| { |
| Type tb = p.type.baseElemOf(); |
| if (tb.isMutable() && tb.hasPointers()) |
| { |
| firstArg = arg; |
| } |
| } |
| } |
| else |
| { |
| // These will be the trailing ... arguments |
| // If not D linkage, do promotions |
| if (tf.linkage != LINK.d) |
| { |
| // Promote bytes, words, etc., to ints |
| arg = integralPromotions(arg, sc); |
| |
| // Promote floats to doubles |
| switch (arg.type.ty) |
| { |
| case Tfloat32: |
| arg = arg.castTo(sc, Type.tfloat64); |
| break; |
| |
| case Timaginary32: |
| arg = arg.castTo(sc, Type.timaginary64); |
| break; |
| |
| default: |
| break; |
| } |
| if (tf.parameterList.varargs == VarArg.variadic) |
| { |
| const(char)* p = tf.linkage == LINK.c ? "extern(C)" : "extern(C++)"; |
| if (arg.type.ty == Tarray) |
| { |
| arg.error("cannot pass dynamic arrays to `%s` vararg functions", p); |
| err = true; |
| } |
| if (arg.type.ty == Tsarray) |
| { |
| arg.error("cannot pass static arrays to `%s` vararg functions", p); |
| err = true; |
| } |
| } |
| } |
| |
| // Do not allow types that need destructors or copy constructors. |
| if (arg.type.needsDestruction()) |
| { |
| arg.error("cannot pass types that need destruction as variadic arguments"); |
| err = true; |
| } |
| if (arg.type.needsCopyOrPostblit()) |
| { |
| arg.error("cannot pass types with postblits or copy constructors as variadic arguments"); |
| err = true; |
| } |
| |
| // Convert static arrays to dynamic arrays |
| // BUG: I don't think this is right for D2 |
| Type tb = arg.type.toBasetype(); |
| if (auto ts = tb.isTypeSArray()) |
| { |
| Type ta = ts.next.arrayOf(); |
| if (ts.size(arg.loc) == 0) |
| arg = new NullExp(arg.loc, ta); |
| else |
| arg = arg.castTo(sc, ta); |
| } |
| if (tb.ty == Tstruct) |
| { |
| //arg = callCpCtor(sc, arg); |
| } |
| // Give error for overloaded function addresses |
| if (auto se = arg.isSymOffExp()) |
| { |
| if (se.hasOverloads && !se.var.isFuncDeclaration().isUnique()) |
| { |
| arg.error("function `%s` is overloaded", arg.toChars()); |
| err = true; |
| } |
| } |
| err |= arg.checkValue(); |
| err |= arg.checkSharedAccess(sc); |
| arg = arg.optimize(WANTvalue); |
| } |
| (*arguments)[i] = arg; |
| } |
| |
| /* If calling C scanf(), printf(), or any variants, check the format string against the arguments |
| */ |
| const isVa_list = tf.parameterList.varargs == VarArg.none; |
| if (fd && fd.printf) |
| { |
| if (auto se = (*arguments)[nparams - 1 - isVa_list].isStringExp()) |
| { |
| checkPrintfFormat(se.loc, se.peekString(), (*arguments)[nparams .. nargs], isVa_list); |
| } |
| } |
| else if (fd && fd.scanf) |
| { |
| if (auto se = (*arguments)[nparams - 1 - isVa_list].isStringExp()) |
| { |
| checkScanfFormat(se.loc, se.peekString(), (*arguments)[nparams .. nargs], isVa_list); |
| } |
| } |
| else |
| { |
| // TODO: not checking the "v" functions yet (for those, check format string only, not args) |
| } |
| |
| /* Remaining problems: |
| * 1. order of evaluation - some function push L-to-R, others R-to-L. Until we resolve what array assignment does (which is |
| * implemented by calling a function) we'll defer this for now. |
| * 2. value structs (or static arrays of them) that need to be copy constructed |
| * 3. value structs (or static arrays of them) that have destructors, and subsequent arguments that may throw before the |
| * function gets called. |
| * 4. value structs need to be destructed after the function call for platforms where the caller destroys the arguments. |
| * 2, 3 and 4 are handled by doing the argument construction in 'eprefix' so that if a later argument throws, they are cleaned |
| * up properly. Pushing arguments on the stack then cannot fail. |
| */ |
| { |
| /* TODO: tackle problem 1) |
| */ |
| const bool leftToRight = true; // TODO: Any cases that need rightToLeft? |
| if (!leftToRight) |
| assert(nargs == nparams); // no variadics for RTL order, as they would probably be evaluated LTR and so add complexity |
| |
| /* Does Problem (4) apply? |
| */ |
| const bool callerDestroysArgs = !target.isCalleeDestroyingArgs(tf); |
| |
| const ptrdiff_t start = (leftToRight ? 0 : cast(ptrdiff_t)nargs - 1); |
| const ptrdiff_t end = (leftToRight ? cast(ptrdiff_t)nargs : -1); |
| const ptrdiff_t step = (leftToRight ? 1 : -1); |
| |
| /* Compute indices of last throwing argument and first arg needing destruction. |
| * Used to not set up destructors unless an arg needs destruction on a throw |
| * in a later argument. |
| */ |
| ptrdiff_t lastthrow = -1; // last argument that may throw |
| ptrdiff_t firstdtor = -1; // first argument that needs destruction |
| ptrdiff_t lastdtor = -1; // last argument that needs destruction |
| for (ptrdiff_t i = start; i != end; i += step) |
| { |
| Expression arg = (*arguments)[i]; |
| if (canThrow(arg, sc.func, false)) |
| lastthrow = i; |
| if (arg.type.needsDestruction()) |
| { |
| Parameter p = (i >= nparams ? null : tf.parameterList[i]); |
| if (!(p && (p.isLazy() || p.isReference()))) |
| { |
| if (firstdtor == -1) |
| firstdtor = i; |
| lastdtor = i; |
| } |
| } |
| } |
| |
| /* Do we need 'eprefix' for problems 3 or 4? |
| */ |
| const bool needsPrefix = callerDestroysArgs |
| ? firstdtor >= 0 // true if any argument needs destruction |
| : firstdtor >= 0 && lastthrow >= 0 && |
| (lastthrow - firstdtor) * step > 0; // last throw after first destruction |
| const ptrdiff_t lastPrefix = callerDestroysArgs |
| ? lastdtor // up to last argument requiring destruction |
| : lastthrow; // up to last potentially throwing argument |
| |
| /* Problem 3: initialize 'eprefix' by declaring the gate |
| */ |
| VarDeclaration gate; |
| if (needsPrefix && !callerDestroysArgs) |
| { |
| // eprefix => bool __gate [= false] |
| Identifier idtmp = Identifier.generateId("__gate"); |
| gate = new VarDeclaration(loc, Type.tbool, idtmp, null); |
| gate.storage_class |= STC.temp | STC.ctfe | STC.volatile_; |
| gate.dsymbolSemantic(sc); |
| |
| auto ae = new DeclarationExp(loc, gate); |
| eprefix = ae.expressionSemantic(sc); |
| } |
| |
| for (ptrdiff_t i = start; i != end; i += step) |
| { |
| Expression arg = (*arguments)[i]; |
| //printf("arg[%d]: %s\n", cast(int)i, arg.toChars()); |
| |
| Parameter parameter = (i >= nparams ? null : tf.parameterList[i]); |
| const bool isRef = parameter && parameter.isReference(); |
| const bool isLazy = parameter && parameter.isLazy(); |
| |
| /* Skip lazy parameters |
| */ |
| if (isLazy) |
| continue; |
| |
| /* Do we have 'eprefix' and aren't past 'lastPrefix' yet? |
| * Then declare a temporary variable for this arg and append that declaration |
| * to 'eprefix', which will implicitly take care of potential problem 2) for |
| * this arg. |
| * 'eprefix' will therefore finally contain all args up to and including 'lastPrefix', |
| * excluding all lazy parameters. |
| */ |
| if (needsPrefix && (lastPrefix - i) * step >= 0) |
| { |
| const bool needsDtor = !isRef && arg.type.needsDestruction() && |
| // Problem 3: last throwing arg doesn't require dtor patching |
| (callerDestroysArgs || i != lastPrefix); |
| |
| /* Declare temporary 'auto __pfx = arg' (needsDtor) or 'auto __pfy = arg' (!needsDtor) |
| */ |
| auto tmp = copyToTemp( |
| (parameter ? parameter.storageClass : tf.parameterList.stc) & (STC.scope_), |
| needsDtor ? "__pfx" : "__pfy", |
| !isRef ? arg : arg.addressOf()); |
| tmp.dsymbolSemantic(sc); |
| |
| if (callerDestroysArgs) |
| { |
| /* Problem 4: Normal temporary, destructed after the call |
| */ |
| if (needsDtor) |
| tmp.isArgDtorVar = true; // mark it so that the backend passes it by ref to the function being called |
| } |
| else |
| { |
| /* Problem 3: Modify the destructor so it only runs if gate==false, |
| * i.e., only if there was a throw while constructing the args |
| */ |
| if (!needsDtor) |
| { |
| if (tmp.edtor) |
| { |
| assert(i == lastPrefix); |
| tmp.edtor = null; |
| } |
| } |
| else |
| { |
| // edtor => (__gate || edtor) |
| assert(tmp.edtor); |
| Expression e = tmp.edtor; |
| e = new LogicalExp(e.loc, EXP.orOr, new VarExp(e.loc, gate), e); |
| tmp.edtor = e.expressionSemantic(sc); |
| //printf("edtor: %s\n", tmp.edtor.toChars()); |
| } |
| } |
| |
| // eprefix => (eprefix, auto __pfx/y = arg) |
| auto ae = new DeclarationExp(loc, tmp); |
| eprefix = Expression.combine(eprefix, ae.expressionSemantic(sc)); |
| |
| // arg => __pfx/y |
| arg = new VarExp(loc, tmp); |
| arg = arg.expressionSemantic(sc); |
| if (isRef) |
| { |
| arg = new PtrExp(loc, arg); |
| arg = arg.expressionSemantic(sc); |
| } |
| |
| /* Problem 3: Last throwing arg? |
| * Then finalize eprefix => (eprefix, gate = true), i.e., disable the |
| * dtors right after constructing the last throwing arg. |
| * From now on, the callee will take care of destructing the args because |
| * the args are implicitly moved into function parameters. |
| */ |
| if (!callerDestroysArgs && i == lastPrefix) |
| { |
| auto e = new AssignExp(gate.loc, new VarExp(gate.loc, gate), IntegerExp.createBool(true)); |
| eprefix = Expression.combine(eprefix, e.expressionSemantic(sc)); |
| } |
| } |
| else // not part of 'eprefix' |
| { |
| /* Handle problem 2) by calling the copy constructor for value structs |
| * (or static arrays of them) if appropriate. |
| */ |
| Type tv = arg.type.baseElemOf(); |
| if (!isRef && tv.ty == Tstruct) |
| arg = doCopyOrMove(sc, arg, parameter ? parameter.type : null); |
| } |
| |
| (*arguments)[i] = arg; |
| } |
| } |
| //if (eprefix) printf("eprefix: %s\n", eprefix.toChars()); |
| |
| /* Test compliance with DIP1021 |
| */ |
| if (global.params.useDIP1021 && |
| tf.trust != TRUST.system && tf.trust != TRUST.trusted) |
| err |= checkMutableArguments(sc, fd, tf, ethis, arguments, false); |
| |
| // If D linkage and variadic, add _arguments[] as first argument |
| if (tf.isDstyleVariadic()) |
| { |
| assert(arguments.dim >= nparams); |
| |
| auto args = new Parameters(arguments.dim - nparams); |
| for (size_t i = 0; i < arguments.dim - nparams; i++) |
| { |
| auto arg = new Parameter(STC.in_, (*arguments)[nparams + i].type, null, null, null); |
| (*args)[i] = arg; |
| } |
| auto tup = new TypeTuple(args); |
| Expression e = (new TypeidExp(loc, tup)).expressionSemantic(sc); |
| arguments.insert(0, e); |
| } |
| |
| /* Determine function return type: tret |
| */ |
| Type tret = tf.next; |
| if (isCtorCall) |
| { |
| //printf("[%s] fd = %s %s, %d %d %d\n", loc.toChars(), fd.toChars(), fd.type.toChars(), |
| // wildmatch, tf.isWild(), fd.isReturnIsolated()); |
| if (!tthis) |
| { |
| assert(sc.intypeof || global.errors); |
| tthis = fd.isThis().type.addMod(fd.type.mod); |
| } |
| if (tf.isWild() && !fd.isReturnIsolated()) |
| { |
| if (wildmatch) |
| tret = tret.substWildTo(wildmatch); |
| int offset; |
| if (!tret.implicitConvTo(tthis) && !(MODimplicitConv(tret.mod, tthis.mod) && tret.isBaseOf(tthis, &offset) && offset == 0)) |
| { |
| const(char)* s1 = tret.isNaked() ? " mutable" : tret.modToChars(); |
| const(char)* s2 = tthis.isNaked() ? " mutable" : tthis.modToChars(); |
| .error(loc, "`inout` constructor `%s` creates%s object, not%s", fd.toPrettyChars(), s1, s2); |
| err = true; |
| } |
| } |
| tret = tthis; |
| } |
| else if (wildmatch && tret) |
| { |
| /* Adjust function return type based on wildmatch |
| */ |
| //printf("wildmatch = x%x, tret = %s\n", wildmatch, tret.toChars()); |
| tret = tret.substWildTo(wildmatch); |
| } |
| |
| *prettype = tret; |
| *peprefix = eprefix; |
| return (err || olderrors != global.errors); |
| } |
| |
| /** |
| * Determines whether a symbol represents a module or package |
| * (Used as a helper for is(type == module) and is(type == package)) |
| * |
| * Params: |
| * sym = the symbol to be checked |
| * |
| * Returns: |
| * the symbol which `sym` represents (or `null` if it doesn't represent a `Package`) |
| */ |
| Package resolveIsPackage(Dsymbol sym) |
| { |
| Package pkg; |
| if (Import imp = sym.isImport()) |
| { |
| if (imp.pkg is null) |
| { |
| .error(sym.loc, "internal compiler error: unable to process forward-referenced import `%s`", |
| imp.toChars()); |
| assert(0); |
| } |
| pkg = imp.pkg; |
| } |
| else if (auto mod = sym.isModule()) |
| pkg = mod.isPackageFile ? mod.pkg : sym.isPackage(); |
| else |
| pkg = sym.isPackage(); |
| if (pkg) |
| pkg.resolvePKGunknown(); |
| return pkg; |
| } |
| |
| private Module loadStdMath() |
| { |
| __gshared Import impStdMath = null; |
| __gshared Identifier[1] stdID; |
| if (!impStdMath) |
| { |
| stdID[0] = Id.std; |
| auto s = new Import(Loc.initial, stdID[], Id.math, null, false); |
| // Module.load will call fatal() if there's no std.math available. |
| // Gag the error here, pushing the error handling to the caller. |
| uint errors = global.startGagging(); |
| s.load(null); |
| if (s.mod) |
| { |
| s.mod.importAll(null); |
| s.mod.dsymbolSemantic(null); |
| } |
| global.endGagging(errors); |
| impStdMath = s; |
| } |
| return impStdMath.mod; |
| } |
| |
| private extern (C++) final class ExpressionSemanticVisitor : Visitor |
| { |
| alias visit = Visitor.visit; |
| |
| Scope* sc; |
| Expression result; |
| |
| this(Scope* sc) |
| { |
| this.sc = sc; |
| } |
| |
| private void setError() |
| { |
| result = ErrorExp.get(); |
| } |
| |
| /************************** |
| * Semantically analyze Expression. |
| * Determine types, fold constants, etc. |
| */ |
| override void visit(Expression e) |
| { |
| static if (LOGSEMANTIC) |
| { |
| printf("Expression::semantic() %s\n", e.toChars()); |
| } |
| if (e.type) |
| e.type = e.type.typeSemantic(e.loc, sc); |
| else |
| e.type = Type.tvoid; |
| result = e; |
| } |
| |
| override void visit(IntegerExp e) |
| { |
| assert(e.type); |
| if (e.type.ty == Terror) |
| return setError(); |
| |
| assert(e.type.deco); |
| e.setInteger(e.getInteger()); |
| result = e; |
| } |
| |
| override void visit(RealExp e) |
| { |
| if (!e.type) |
| e.type = Type.tfloat64; |
| else |
| e.type = e.type.typeSemantic(e.loc, sc); |
| result = e; |
| } |
| |
| override void visit(ComplexExp e) |
| { |
| if (!e.type) |
| e.type = Type.tcomplex80; |
| else |
| e.type = e.type.typeSemantic(e.loc, sc); |
| result = e; |
| } |
| |
| override void visit(IdentifierExp exp) |
| { |
| static if (LOGSEMANTIC) |
| { |
| printf("IdentifierExp::semantic('%s')\n", exp.ident.toChars()); |
| } |
| if (exp.type) // This is used as the dummy expression |
| { |
| result = exp; |
| return; |
| } |
| |
| Dsymbol scopesym; |
| Dsymbol s = sc.search(exp.loc, exp.ident, &scopesym); |
| if (s) |
| { |
| if (s.errors) |
| return setError(); |
| |
| Expression e; |
| |
| /* See if the symbol was a member of an enclosing 'with' |
| */ |
| WithScopeSymbol withsym = scopesym.isWithScopeSymbol(); |
| if (withsym && withsym.withstate.wthis && symbolIsVisible(sc, s)) |
| { |
| /* Disallow shadowing |
| */ |
| // First find the scope of the with |
| Scope* scwith = sc; |
| while (scwith.scopesym != scopesym) |
| { |
| scwith = scwith.enclosing; |
| assert(scwith); |
| } |
| // Look at enclosing scopes for symbols with the same name, |
| // in the same function |
| for (Scope* scx = scwith; scx && scx.func == scwith.func; scx = scx.enclosing) |
| { |
| Dsymbol s2; |
| if (scx.scopesym && scx.scopesym.symtab && (s2 = scx.scopesym.symtab.lookup(s.ident)) !is null && s != s2) |
| { |
| exp.error("with symbol `%s` is shadowing local symbol `%s`", s.toPrettyChars(), s2.toPrettyChars()); |
| return setError(); |
| } |
| } |
| s = s.toAlias(); |
| |
| // Same as wthis.ident |
| // TODO: DotIdExp.semantic will find 'ident' from 'wthis' again. |
| // The redudancy should be removed. |
| e = new VarExp(exp.loc, withsym.withstate.wthis); |
| e = new DotIdExp(exp.loc, e, exp.ident); |
| e = e.expressionSemantic(sc); |
| } |
| else |
| { |
| if (withsym) |
| { |
| if (withsym.withstate.exp.type.ty != Tvoid) |
| { |
| // 'with (exp)' is a type expression |
| // or 's' is not visible there (for error message) |
| e = new TypeExp(exp.loc, withsym.withstate.exp.type); |
| } |
| else |
| { |
| // 'with (exp)' is a Package/Module |
| e = withsym.withstate.exp; |
| } |
| e = new DotIdExp(exp.loc, e, exp.ident); |
| result = e.expressionSemantic(sc); |
| return; |
| } |
| |
| /* If f is really a function template, |
| * then replace f with the function template declaration. |
| */ |
| FuncDeclaration f = s.isFuncDeclaration(); |
| if (f) |
| { |
| TemplateDeclaration td = getFuncTemplateDecl(f); |
| if (td) |
| { |
| if (td.overroot) // if not start of overloaded list of TemplateDeclaration's |
| td = td.overroot; // then get the start |
| e = new TemplateExp(exp.loc, td, f); |
| e = e.expressionSemantic(sc); |
| result = e; |
| return; |
| } |
| } |
| |
| if (global.params.fixAliasThis) |
| { |
| ExpressionDsymbol expDsym = scopesym.isExpressionDsymbol(); |
| if (expDsym) |
| { |
| //printf("expDsym = %s\n", expDsym.exp.toChars()); |
| result = expDsym.exp.expressionSemantic(sc); |
| return; |
| } |
| } |
| // Haven't done overload resolution yet, so pass 1 |
| e = symbolToExp(s, exp.loc, sc, true); |
| } |
| result = e; |
| return; |
| } |
| |
| if (!global.params.fixAliasThis && hasThis(sc)) |
| { |
| for (AggregateDeclaration ad = sc.getStructClassScope(); ad;) |
| { |
| if (ad.aliasthis) |
| { |
| Expression e; |
| e = new ThisExp(exp.loc); |
| e = new DotIdExp(exp.loc, e, ad.aliasthis.ident); |
| e = new DotIdExp(exp.loc, e, exp.ident); |
| e = e.trySemantic(sc); |
| if (e) |
| { |
| result = e; |
| return; |
| } |
| } |
| |
| auto cd = ad.isClassDeclaration(); |
| if (cd && cd.baseClass && cd.baseClass != ClassDeclaration.object) |
| { |
| ad = cd.baseClass; |
| continue; |
| } |
| break; |
| } |
| } |
| |
| if (exp.ident == Id.ctfe) |
| { |
| if (sc.flags & SCOPE.ctfe) |
| { |
| exp.error("variable `__ctfe` cannot be read at compile time"); |
| return setError(); |
| } |
| |
| // Create the magic __ctfe bool variable |
| auto vd = new VarDeclaration(exp.loc, Type.tbool, Id.ctfe, null); |
| vd.storage_class |= STC.temp; |
| vd.semanticRun = PASS.semanticdone; |
| Expression e = new VarExp(exp.loc, vd); |
| e = e.expressionSemantic(sc); |
| result = e; |
| return; |
| } |
| |
| // If we've reached this point and are inside a with() scope then we may |
| // try one last attempt by checking whether the 'wthis' object supports |
| // dynamic dispatching via opDispatch. |
| // This is done by rewriting this expression as wthis.ident. |
| // The innermost with() scope of the hierarchy to satisfy the condition |
| // above wins. |
| // https://issues.dlang.org/show_bug.cgi?id=6400 |
| for (Scope* sc2 = sc; sc2; sc2 = sc2.enclosing) |
| { |
| if (!sc2.scopesym) |
| continue; |
| |
| if (auto ss = sc2.scopesym.isWithScopeSymbol()) |
| { |
| if (ss.withstate.wthis) |
| { |
| Expression e; |
| e = new VarExp(exp.loc, ss.withstate.wthis); |
| e = new DotIdExp(exp.loc, e, exp.ident); |
| e = e.trySemantic(sc); |
| if (e) |
| { |
| result = e; |
| return; |
| } |
| } |
| // Try Type.opDispatch (so the static version) |
| else if (ss.withstate.exp && ss.withstate.exp.op == EXP.type) |
| { |
| if (Type t = ss.withstate.exp.isTypeExp().type) |
| { |
| Expression e; |
| e = new TypeExp(exp.loc, t); |
| e = new DotIdExp(exp.loc, e, exp.ident); |
| e = e.trySemantic(sc); |
| if (e) |
| { |
| result = e; |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| /* Look for what user might have meant |
| */ |
| if (const n = importHint(exp.ident.toString())) |
| exp.error("`%s` is not defined, perhaps `import %.*s;` is needed?", exp.ident.toChars(), cast(int)n.length, n.ptr); |
| else if (auto s2 = sc.search_correct(exp.ident)) |
| exp.error("undefined identifier `%s`, did you mean %s `%s`?", exp.ident.toChars(), s2.kind(), s2.toChars()); |
| else if (const p = Scope.search_correct_C(exp.ident)) |
| exp.error("undefined identifier `%s`, did you mean `%s`?", exp.ident.toChars(), p); |
| else if (exp.ident == Id.dollar) |
| exp.error("undefined identifier `$`"); |
| else |
| exp.error("undefined identifier `%s`", exp.ident.toChars()); |
| |
| result = ErrorExp.get(); |
| } |
| |
| override void visit(DsymbolExp e) |
| { |
| result = symbolToExp(e.s, e.loc, sc, e.hasOverloads); |
| } |
| |
| override void visit(ThisExp e) |
| { |
| static if (LOGSEMANTIC) |
| { |
| printf("ThisExp::semantic()\n"); |
| } |
| if (e.type) |
| { |
| result = e; |
| return; |
| } |
| |
| FuncDeclaration fd = hasThis(sc); // fd is the uplevel function with the 'this' variable |
| AggregateDeclaration ad; |
| |
| /* Special case for typeof(this) and typeof(super) since both |
| * should work even if they are not inside a non-static member function |
| */ |
| if (!fd && sc.intypeof == 1) |
| { |
| // Find enclosing struct or class |
| for (Dsymbol s = sc.getStructClassScope(); 1; s = s.parent) |
| { |
| if (!s) |
| { |
| e.error("`%s` is not in a class or struct scope", e.toChars()); |
| goto Lerr; |
| }
|