From c013060f0e062945f697ab1b93bfcc791ea3c557 Mon Sep 17 00:00:00 2001 From: Bastiaan Veelo Date: Sun, 3 Mar 2024 09:48:07 +0100 Subject: [PATCH] Move functions from func.d to funcsem.d. These require `import dscope` that we want to remove from func.d in pursuit of https://github.com/orgs/dlang/projects/41. Other functions that still need to be moved include `overloadApply()`, `isUnique()` and `equals()`, the latter needing a visitor akin #15782. --- compiler/src/dmd/dclass.d | 1 + compiler/src/dmd/declaration.d | 1 + compiler/src/dmd/dmangle.d | 1 + compiler/src/dmd/dstruct.d | 1 + compiler/src/dmd/escape.d | 1 + compiler/src/dmd/func.d | 863 --------------------------------- compiler/src/dmd/funcsem.d | 811 +++++++++++++++++++++++++++++++ compiler/src/dmd/opover.d | 1 + compiler/src/dmd/safe.d | 2 +- compiler/src/dmd/semantic2.d | 1 + 10 files changed, 819 insertions(+), 864 deletions(-) diff --git a/compiler/src/dmd/dclass.d b/compiler/src/dmd/dclass.d index 8bac1f4ea266..2199d5d1a777 100644 --- a/compiler/src/dmd/dclass.d +++ b/compiler/src/dmd/dclass.d @@ -26,6 +26,7 @@ import dmd.dsymbol; import dmd.dsymbolsem; import dmd.errors; import dmd.func; +import dmd.funcsem; import dmd.id; import dmd.identifier; import dmd.location; diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index 384d9118861f..75e7e911a697 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -27,6 +27,7 @@ import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; +import dmd.funcsem; import dmd.globals; import dmd.gluelayer; import dmd.id; diff --git a/compiler/src/dmd/dmangle.d b/compiler/src/dmd/dmangle.d index 33428ded2d1d..3640b67c7a91 100644 --- a/compiler/src/dmd/dmangle.d +++ b/compiler/src/dmd/dmangle.d @@ -147,6 +147,7 @@ import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; +import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier; diff --git a/compiler/src/dmd/dstruct.d b/compiler/src/dmd/dstruct.d index df4d07a81d99..e4a33dd98157 100644 --- a/compiler/src/dmd/dstruct.d +++ b/compiler/src/dmd/dstruct.d @@ -28,6 +28,7 @@ import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; +import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier; diff --git a/compiler/src/dmd/escape.d b/compiler/src/dmd/escape.d index 3e17ff4736d3..4e1bc59987e4 100644 --- a/compiler/src/dmd/escape.d +++ b/compiler/src/dmd/escape.d @@ -25,6 +25,7 @@ import dmd.dsymbol; import dmd.errors; import dmd.expression; import dmd.func; +import dmd.funcsem; import dmd.globals : FeatureState; import dmd.id; import dmd.identifier; diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 7003c2b11923..382710d78393 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -512,149 +512,6 @@ extern (C++) class FuncDeclaration : Declaration return true; } - /******************************************** - * Find function in overload list that exactly matches t. - */ - extern (D) final FuncDeclaration overloadExactMatch(Type t) - { - FuncDeclaration fd; - overloadApply(this, (Dsymbol s) - { - auto f = s.isFuncDeclaration(); - if (!f) - return 0; - if (f.storage_class & STC.disable) - return 0; - if (t.equals(f.type)) - { - fd = f; - return 1; - } - - /* Allow covariant matches, as long as the return type - * is just a const conversion. - * This allows things like pure functions to match with an impure function type. - */ - if (t.ty == Tfunction) - { - auto tf = cast(TypeFunction)f.type; - if (tf.covariant(t) == Covariant.yes && - tf.nextOf().implicitConvTo(t.nextOf()) >= MATCH.constant) - { - fd = f; - return 1; - } - } - return 0; - }); - return fd; - } - - /******************************************** - * Find function in overload list that matches to the 'this' modifier. - * There's four result types. - * - * 1. If the 'tthis' matches only one candidate, it's an "exact match". - * Returns the function and 'hasOverloads' is set to false. - * eg. If 'tthis" is mutable and there's only one mutable method. - * 2. If there's two or more match candidates, but a candidate function will be - * a "better match". - * Returns the better match function but 'hasOverloads' is set to true. - * eg. If 'tthis' is mutable, and there's both mutable and const methods, - * the mutable method will be a better match. - * 3. If there's two or more match candidates, but there's no better match, - * Returns null and 'hasOverloads' is set to true to represent "ambiguous match". - * eg. If 'tthis' is mutable, and there's two or more mutable methods. - * 4. If there's no candidates, it's "no match" and returns null with error report. - * e.g. If 'tthis' is const but there's no const methods. - */ - extern (D) final FuncDeclaration overloadModMatch(const ref Loc loc, Type tthis, ref bool hasOverloads) - { - //printf("FuncDeclaration::overloadModMatch('%s')\n", toChars()); - MatchAccumulator m; - overloadApply(this, (Dsymbol s) - { - auto f = s.isFuncDeclaration(); - if (!f || f == m.lastf) // skip duplicates - return 0; - - auto tf = f.type.toTypeFunction(); - //printf("tf = %s\n", tf.toChars()); - - MATCH match; - if (tthis) // non-static functions are preferred than static ones - { - if (f.needThis()) - match = f.isCtorDeclaration() ? MATCH.exact : MODmethodConv(tthis.mod, tf.mod); - else - match = MATCH.constant; // keep static function in overload candidates - } - else // static functions are preferred than non-static ones - { - if (f.needThis()) - match = MATCH.convert; - else - match = MATCH.exact; - } - if (match == MATCH.nomatch) - return 0; - - if (match > m.last) goto LcurrIsBetter; - if (match < m.last) goto LlastIsBetter; - - // See if one of the matches overrides the other. - if (m.lastf.overrides(f)) goto LlastIsBetter; - if (f.overrides(m.lastf)) goto LcurrIsBetter; - - //printf("\tambiguous\n"); - m.nextf = f; - m.count++; - return 0; - - LlastIsBetter: - //printf("\tlastbetter\n"); - m.count++; // count up - return 0; - - LcurrIsBetter: - //printf("\tisbetter\n"); - if (m.last <= MATCH.convert) - { - // clear last secondary matching - m.nextf = null; - m.count = 0; - } - m.last = match; - m.lastf = f; - m.count++; // count up - return 0; - }); - - if (m.count == 1) // exact match - { - hasOverloads = false; - } - else if (m.count > 1) // better or ambiguous match - { - hasOverloads = true; - } - else // no match - { - hasOverloads = true; - auto tf = this.type.toTypeFunction(); - assert(tthis); - assert(!MODimplicitConv(tthis.mod, tf.mod)); // modifier mismatch - { - OutBuffer thisBuf, funcBuf; - MODMatchToBuffer(&thisBuf, tthis.mod, tf.mod); - MODMatchToBuffer(&funcBuf, tf.mod, tthis.mod); - .error(loc, "%smethod %s is not callable using a %sobject", kind, toPrettyChars, - funcBuf.peekChars(), this.toPrettyChars(), thisBuf.peekChars()); - } - } - return m.lastf; - } - /******************************************** * find function template root in overload list */ @@ -855,43 +712,6 @@ extern (C++) class FuncDeclaration : Declaration return level; } - /*********************************** - * Determine lexical level difference from `this` to nested function `fd`. - * Issue error if `this` cannot call `fd`. - * - * Params: - * loc = location for error messages - * sc = context - * fd = target of call - * decl = The `Declaration` that triggered this check. - * Used to provide a better error message only. - * Returns: - * 0 same level - * >0 decrease nesting by number - * -1 increase nesting by 1 (`fd` is nested within 'this') - * LevelError error - */ - extern (D) final int getLevelAndCheck(const ref Loc loc, Scope* sc, FuncDeclaration fd, - Declaration decl) - { - int level = getLevel(fd, sc.intypeof); - if (level != LevelError) - return level; - - // Don't give error if in template constraint - if (!(sc.flags & SCOPE.constraint)) - { - const(char)* xstatic = isStatic() ? "`static` " : ""; - // better diagnostics for static functions - .error(loc, "%s%s `%s` cannot access %s `%s` in frame of function `%s`", - xstatic, kind(), toPrettyChars(), decl.kind(), decl.toChars(), - fd.toPrettyChars()); - .errorSupplemental(decl.loc, "`%s` declared here", decl.toChars()); - return LevelError; - } - return 1; - } - enum LevelError = -2; override const(char)* toPrettyChars(bool QualifyTypes = false) @@ -990,47 +810,6 @@ extern (C++) class FuncDeclaration : Declaration return false; } - /********************************** - * Decide if attributes for this function can be inferred from examining - * the function body. - * Returns: - * true if can - */ - final bool canInferAttributes(Scope* sc) - { - if (!fbody) - return false; - - if (isVirtualMethod() && - /* - * https://issues.dlang.org/show_bug.cgi?id=21719 - * - * If we have an auto virtual function we can infer - * the attributes. - */ - !(inferRetType && !isCtorDeclaration())) - return false; // since they may be overridden - - if (sc.func && - /********** this is for backwards compatibility for the moment ********/ - (!isMember() || sc.func.isSafeBypassingInference() && !isInstantiated())) - return true; - - if (isFuncLiteralDeclaration() || // externs are not possible with literals - (storage_class & STC.inference) || // do attribute inference - (inferRetType && !isCtorDeclaration())) - return true; - - if (isInstantiated()) - { - auto ti = parent.isTemplateInstance(); - if (ti is null || ti.isTemplateMixin() || ti.tempdecl.ident == ident) - return true; - } - - return false; - } - /***************************************** * Initialize for inferring the attributes of this function. */ @@ -1633,101 +1412,6 @@ extern (C++) class FuncDeclaration : Declaration return result; } - /********************************************* - * In the current function, we are calling 'this' function. - * 1. Check to see if the current function can call 'this' function, issue error if not. - * 2. If the current function is not the parent of 'this' function, then add - * the current function to the list of siblings of 'this' function. - * 3. If the current function is a literal, and it's accessing an uplevel scope, - * then mark it as a delegate. - * Returns true if error occurs. - */ - extern (D) final bool checkNestedReference(Scope* sc, const ref Loc loc) - { - //printf("FuncDeclaration::checkNestedReference() %s\n", toPrettyChars()); - - if (auto fld = this.isFuncLiteralDeclaration()) - { - if (fld.tok == TOK.reserved) - { - fld.tok = TOK.function_; - fld.vthis = null; - } - } - - if (!parent || parent == sc.parent) - return false; - if (ident == Id.require || ident == Id.ensure) - return false; - if (!isThis() && !isNested()) - return false; - - // The current function - FuncDeclaration fdthis = sc.parent.isFuncDeclaration(); - if (!fdthis) - return false; // out of function scope - - Dsymbol p = toParentLocal(); - Dsymbol p2 = toParent2(); - - // Function literals from fdthis to p must be delegates - ensureStaticLinkTo(fdthis, p); - if (p != p2) - ensureStaticLinkTo(fdthis, p2); - - if (isNested()) - { - // The function that this function is in - bool checkEnclosing(FuncDeclaration fdv) - { - if (!fdv) - return false; - if (fdv == fdthis) - return false; - - //printf("this = %s in [%s]\n", this.toChars(), this.loc.toChars()); - //printf("fdv = %s in [%s]\n", fdv .toChars(), fdv .loc.toChars()); - //printf("fdthis = %s in [%s]\n", fdthis.toChars(), fdthis.loc.toChars()); - - // Add this function to the list of those which called us - if (fdthis != this) - { - bool found = false; - for (size_t i = 0; i < siblingCallers.length; ++i) - { - if (siblingCallers[i] == fdthis) - found = true; - } - if (!found) - { - //printf("\tadding sibling %s to %s\n", fdthis.toPrettyChars(), toPrettyChars()); - if (!sc.intypeof && !(sc.flags & SCOPE.compile)) - { - siblingCallers.push(fdthis); - computedEscapingSiblings = false; - } - } - } - - const lv = fdthis.getLevelAndCheck(loc, sc, fdv, this); - if (lv == LevelError) - return true; // error - if (lv == -1) - return false; // downlevel call - if (lv == 0) - return false; // same level call - - return false; // Uplevel call - } - - if (checkEnclosing(p.isFuncDeclaration())) - return true; - if (checkEnclosing(p == p2 ? null : p2.isFuncDeclaration())) - return true; - } - return false; - } - /******************************* * Look at all the variables in this function that are referenced * by nested functions, and determine if a closure needs to be @@ -1922,147 +1606,6 @@ extern (C++) class FuncDeclaration : Declaration return false; } - /**************************************************** - * Check whether result variable can be built. - * Returns: - * `true` if the function has a return type that - * is different from `void`. - */ - extern (D) private bool canBuildResultVar() - { - auto f = cast(TypeFunction)type; - return f && f.nextOf() && f.nextOf().toBasetype().ty != Tvoid; - } - - /**************************************************** - * Merge into this function the 'in' contracts of all it overrides. - * 'in's are OR'd together, i.e. only one of them needs to pass. - */ - extern (D) final Statement mergeFrequire(Statement sf, Expressions* params) - { - /* If a base function and its override both have an IN contract, then - * only one of them needs to succeed. This is done by generating: - * - * void derived.in() { - * try { - * base.in(); - * } - * catch () { - * ... body of derived.in() ... - * } - * } - * - * So if base.in() doesn't throw, derived.in() need not be executed, and the contract is valid. - * If base.in() throws, then derived.in()'s body is executed. - */ - - foreach (fdv; foverrides) - { - /* The semantic pass on the contracts of the overridden functions must - * be completed before code generation occurs. - * https://issues.dlang.org/show_bug.cgi?id=3602 - */ - if (fdv.frequires && fdv.semanticRun != PASS.semantic3done) - { - assert(fdv._scope); - Scope* sc = fdv._scope.push(); - sc.stc &= ~STC.override_; - fdv.semantic3(sc); - sc.pop(); - } - - sf = fdv.mergeFrequire(sf, params); - if (!sf || !fdv.fdrequire) - return null; - //printf("fdv.frequire: %s\n", fdv.frequire.toChars()); - /* Make the call: - * try { __require(params); } - * catch (Throwable) { frequire; } - */ - params = Expression.arraySyntaxCopy(params); - Expression e = new CallExp(loc, new VarExp(loc, fdv.fdrequire, false), params); - Statement s2 = new ExpStatement(loc, e); - - auto c = new Catch(loc, getThrowable(), null, sf); - c.internalCatch = true; - auto catches = new Catches(); - catches.push(c); - sf = new TryCatchStatement(loc, s2, catches); - } - return sf; - } - - /**************************************************** - * Merge into this function the 'in' contracts of all it overrides. - */ - extern (D) final Statement mergeFrequireInclusivePreview(Statement sf, Expressions* params) - { - /* If a base function and its override both have an IN contract, then - * the override in contract must widen the guarantee of the base contract. - * This is checked by generating: - * - * void derived.in() { - * try { - * ... body of derived.in() ... - * } - * catch () { - * // derived in rejected this argument. so parent must also reject it, or we've tightened the contract. - * base.in(); - * assert(false, "Logic error: " ~ thr.msg); - * } - * } - */ - - foreach (fdv; foverrides) - { - /* The semantic pass on the contracts of the overridden functions must - * be completed before code generation occurs. - * https://issues.dlang.org/show_bug.cgi?id=3602 - */ - if (fdv.frequires && fdv.semanticRun != PASS.semantic3done) - { - assert(fdv._scope); - Scope* sc = fdv._scope.push(); - sc.stc &= ~STC.override_; - fdv.semantic3(sc); - sc.pop(); - } - - sf = fdv.mergeFrequireInclusivePreview(sf, params); - if (sf && fdv.fdrequire) - { - const loc = this.fdrequire.loc; - - //printf("fdv.frequire: %s\n", fdv.frequire.toChars()); - /* Make the call: - * try { frequire; } - * catch (Throwable thr) { __require(params); assert(false, "Logic error: " ~ thr.msg); } - */ - Identifier id = Identifier.generateId("thr"); - params = Expression.arraySyntaxCopy(params); - Expression e = new CallExp(loc, new VarExp(loc, fdv.fdrequire, false), params); - Statement s2 = new ExpStatement(loc, e); - // assert(false, ...) - // TODO make this a runtime helper to allow: - // - chaining the original expression - // - nogc concatenation - Expression msg = new StringExp(loc, "Logic error: in-contract was tighter than parent in-contract"); - Statement fail = new ExpStatement(loc, new AssertExp(loc, IntegerExp.literal!0, msg)); - - Statement s3 = new CompoundStatement(loc, s2, fail); - - auto c = new Catch(loc, getThrowable(), id, s3); - c.internalCatch = true; - auto catches = new Catches(); - catches.push(c); - sf = new TryCatchStatement(loc, sf, catches); - } - else - return null; - } - return sf; - } - /**************************************************** * Determine whether an 'out' contract is declared inside * the given function or any of its overrides. @@ -2084,232 +1627,6 @@ extern (C++) class FuncDeclaration : Declaration return false; } - /**************************************************** - * Rewrite contracts as statements. - */ - final void buildEnsureRequire() - { - - if (frequires) - { - /* in { statements1... } - * in { statements2... } - * ... - * becomes: - * in { { statements1... } { statements2... } ... } - */ - assert(frequires.length); - auto loc = (*frequires)[0].loc; - auto s = new Statements; - foreach (r; *frequires) - { - s.push(new ScopeStatement(r.loc, r, r.loc)); - } - frequire = new CompoundStatement(loc, s); - } - - if (fensures) - { - /* out(id1) { statements1... } - * out(id2) { statements2... } - * ... - * becomes: - * out(__result) { { ref id1 = __result; { statements1... } } - * { ref id2 = __result; { statements2... } } ... } - */ - assert(fensures.length); - auto loc = (*fensures)[0].ensure.loc; - auto s = new Statements; - foreach (r; *fensures) - { - if (r.id && canBuildResultVar()) - { - auto rloc = r.ensure.loc; - auto resultId = new IdentifierExp(rloc, Id.result); - auto init = new ExpInitializer(rloc, resultId); - auto stc = STC.ref_ | STC.temp | STC.result; - auto decl = new VarDeclaration(rloc, null, r.id, init, stc); - auto sdecl = new ExpStatement(rloc, decl); - s.push(new ScopeStatement(rloc, new CompoundStatement(rloc, sdecl, r.ensure), rloc)); - } - else - { - s.push(r.ensure); - } - } - fensure = new CompoundStatement(loc, s); - } - - if (!isVirtual()) - return; - - /* Rewrite contracts as nested functions, then call them. Doing it as nested - * functions means that overriding functions can call them. - */ - TypeFunction f = cast(TypeFunction) type; - - /* Make a copy of the parameters and make them all ref */ - static Parameters* toRefCopy(ParameterList parameterList) - { - auto result = new Parameters(); - - foreach (n, p; parameterList) - { - p = p.syntaxCopy(); - if (!p.isLazy()) - p.storageClass = (p.storageClass | STC.ref_) & ~STC.out_; - p.defaultArg = null; // won't be the same with ref - result.push(p); - } - - return result; - } - - if (frequire) - { - /* in { ... } - * becomes: - * void __require(ref params) { ... } - * __require(params); - */ - Loc loc = frequire.loc; - fdrequireParams = new Expressions(); - if (parameters) - { - foreach (vd; *parameters) - fdrequireParams.push(new VarExp(loc, vd)); - } - auto fo = cast(TypeFunction)(originalType ? originalType : f); - auto fparams = toRefCopy(fo.parameterList); - auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d); - tf.isnothrow = f.isnothrow; - tf.isnogc = f.isnogc; - tf.purity = f.purity; - tf.trust = f.trust; - auto fd = new FuncDeclaration(loc, loc, Id.require, STC.undefined_, tf); - fd.fbody = frequire; - Statement s1 = new ExpStatement(loc, fd); - Expression e = new CallExp(loc, new VarExp(loc, fd, false), fdrequireParams); - Statement s2 = new ExpStatement(loc, e); - frequire = new CompoundStatement(loc, s1, s2); - fdrequire = fd; - } - - /* We need to set fdensureParams here and not in the block below to - * have the parameters available when calling a base class ensure(), - * even if this function doesn't have an out contract. - */ - fdensureParams = new Expressions(); - if (canBuildResultVar()) - fdensureParams.push(new IdentifierExp(loc, Id.result)); - if (parameters) - { - foreach (vd; *parameters) - fdensureParams.push(new VarExp(loc, vd)); - } - - if (fensure) - { - /* out (result) { ... } - * becomes: - * void __ensure(ref tret result, ref params) { ... } - * __ensure(result, params); - */ - Loc loc = fensure.loc; - auto fparams = new Parameters(); - if (canBuildResultVar()) - { - Parameter p = new Parameter(loc, STC.ref_ | STC.const_, f.nextOf(), Id.result, null, null); - fparams.push(p); - } - auto fo = cast(TypeFunction)(originalType ? originalType : f); - fparams.pushSlice((*toRefCopy(fo.parameterList))[]); - auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d); - tf.isnothrow = f.isnothrow; - tf.isnogc = f.isnogc; - tf.purity = f.purity; - tf.trust = f.trust; - auto fd = new FuncDeclaration(loc, loc, Id.ensure, STC.undefined_, tf); - fd.fbody = fensure; - Statement s1 = new ExpStatement(loc, fd); - Expression e = new CallExp(loc, new VarExp(loc, fd, false), fdensureParams); - Statement s2 = new ExpStatement(loc, e); - fensure = new CompoundStatement(loc, s1, s2); - fdensure = fd; - } - } - - /**************************************************** - * Merge into this function the 'out' contracts of all it overrides. - * 'out's are AND'd together, i.e. all of them need to pass. - */ - extern (D) final Statement mergeFensure(Statement sf, Identifier oid, Expressions* params) - { - /* Same comments as for mergeFrequire(), except that we take care - * of generating a consistent reference to the 'result' local by - * explicitly passing 'result' to the nested function as a reference - * argument. - * This won't work for the 'this' parameter as it would require changing - * the semantic code for the nested function so that it looks on the parameter - * list for the 'this' pointer, something that would need an unknown amount - * of tweaking of various parts of the compiler that I'd rather leave alone. - */ - foreach (fdv; foverrides) - { - /* The semantic pass on the contracts of the overridden functions must - * be completed before code generation occurs. - * https://issues.dlang.org/show_bug.cgi?id=3602 and - * https://issues.dlang.org/show_bug.cgi?id=5230 - */ - if (needsFensure(fdv) && fdv.semanticRun != PASS.semantic3done) - { - assert(fdv._scope); - Scope* sc = fdv._scope.push(); - sc.stc &= ~STC.override_; - fdv.semantic3(sc); - sc.pop(); - } - - sf = fdv.mergeFensure(sf, oid, params); - if (fdv.fdensure) - { - //printf("fdv.fensure: %s\n", fdv.fensure.toChars()); - // Make the call: __ensure(result, params) - params = Expression.arraySyntaxCopy(params); - if (canBuildResultVar()) - { - Type t1 = fdv.type.nextOf().toBasetype(); - Type t2 = this.type.nextOf().toBasetype(); - if (t1.isBaseOf(t2, null)) - { - /* Making temporary reference variable is necessary - * in covariant return. - * https://issues.dlang.org/show_bug.cgi?id=5204 - * https://issues.dlang.org/show_bug.cgi?id=10479 - */ - Expression* eresult = &(*params)[0]; - auto ei = new ExpInitializer(Loc.initial, *eresult); - auto v = new VarDeclaration(Loc.initial, t1, Identifier.generateId("__covres"), ei); - v.storage_class |= STC.temp; - auto de = new DeclarationExp(Loc.initial, v); - auto ve = new VarExp(Loc.initial, v); - *eresult = new CommaExp(Loc.initial, de, ve); - } - } - Expression e = new CallExp(loc, new VarExp(loc, fdv.fdensure, false), params); - Statement s2 = new ExpStatement(loc, e); - - if (sf) - { - sf = new CompoundStatement(sf.loc, s2, sf); - } - else - sf = s2; - } - } - return sf; - } - /********************************************* * Returns: the function's parameter list, and whether * it is variadic or not. @@ -3073,56 +2390,6 @@ extern (C++) final class FuncLiteralDeclaration : FuncDeclaration return false; } - /******************************* - * Modify all expression type of return statements to tret. - * - * On function literals, return type may be modified based on the context type - * after its semantic3 is done, in FuncExp::implicitCastTo. - * - * A function() dg = (){ return new B(); } // OK if is(B : A) == true - * - * If B to A conversion is convariant that requires offseet adjusting, - * all return statements should be adjusted to return expressions typed A. - */ - extern (D) void modifyReturns(Scope* sc, Type tret) - { - import dmd.statement_rewrite_walker; - - extern (C++) final class RetWalker : StatementRewriteWalker - { - alias visit = typeof(super).visit; - public: - Scope* sc; - Type tret; - FuncLiteralDeclaration fld; - - override void visit(ReturnStatement s) - { - Expression exp = s.exp; - if (exp && !exp.type.equals(tret)) - s.exp = exp.implicitCastTo(sc, tret); - } - } - - if (semanticRun < PASS.semantic3done) - return; - - if (fes) - return; - - scope RetWalker w = new RetWalker(); - w.sc = sc; - w.tret = tret; - w.fld = this; - fbody.accept(w); - - // Also update the inferred function type to match the new return type. - // This is required so the code generator does not try to cast the - // modified returns back to the original type. - if (inferRetType && type.nextOf() != tret) - type.toTypeFunction().next = tret; - } - override inout(FuncLiteralDeclaration) isFuncLiteralDeclaration() inout { return this; @@ -3654,136 +2921,6 @@ extern (C++) final class NewDeclaration : FuncDeclaration } } -/************************************** - * When a traits(compiles) is used on a function literal call - * we need to take into account if the body of the function - * violates any attributes, however, we must not affect the - * attribute inference on the outer function. The attributes - * of the function literal still need to be inferred, therefore - * we need a way to check for the scope that the traits compiles - * introduces. - * - * Params: - * sc = scope to be checked for - * - * Returns: `true` if the provided scope is the root - * of the traits compiles list of scopes. - */ -bool isRootTraitsCompilesScope(Scope* sc) -{ - return (sc.flags & SCOPE.compile) && !(sc.func.flags & SCOPE.compile); -} - -/************************************** - * A statement / expression in this scope is not `@safe`, - * so mark the enclosing function as `@system` - * - * Params: - * sc = scope that the unsafe statement / expression is in - * gag = surpress error message (used in escape.d) - * loc = location of error - * fmt = printf-style format string - * arg0 = (optional) argument for first %s format specifier - * arg1 = (optional) argument for second %s format specifier - * arg2 = (optional) argument for third %s format specifier - * Returns: whether there's a safe error - */ -bool setUnsafe(Scope* sc, - bool gag = false, Loc loc = Loc.init, const(char)* fmt = null, - RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) -{ - if (sc.intypeof) - return false; // typeof(cast(int*)0) is safe - - if (sc.flags & SCOPE.debug_) // debug {} scopes are permissive - return false; - - if (!sc.func) - { - if (sc.varDecl) - { - if (sc.varDecl.storage_class & STC.safe) - { - .error(loc, fmt, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); - return true; - } - else if (!(sc.varDecl.storage_class & STC.trusted)) - { - sc.varDecl.storage_class |= STC.system; - sc.varDecl.systemInferred = true; - } - } - return false; - } - - - if (isRootTraitsCompilesScope(sc)) // __traits(compiles, x) - { - if (sc.func.isSafeBypassingInference()) - { - // Message wil be gagged, but still call error() to update global.errors and for - // -verrors=spec - .error(loc, fmt, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); - return true; - } - return false; - } - - return sc.func.setUnsafe(gag, loc, fmt, arg0, arg1, arg2); -} - -/*************************************** - * Like `setUnsafe`, but for safety errors still behind preview switches - * - * Given a `FeatureState fs`, for example dip1000 / dip25 / systemVariables, - * the behavior changes based on the setting: - * - * - In case of `-revert=fs`, it does nothing. - * - In case of `-preview=fs`, it's the same as `setUnsafe` - * - By default, print a deprecation in `@safe` functions, or store an attribute violation in inferred functions. - * - * Params: - * sc = used to find affected function/variable, and for checking whether we are in a deprecated / speculative scope - * fs = feature state from the preview flag - * gag = surpress error message - * loc = location of error - * msg = printf-style format string - * arg0 = (optional) argument for first %s format specifier - * arg1 = (optional) argument for second %s format specifier - * arg2 = (optional) argument for third %s format specifier - * Returns: whether an actual safe error (not deprecation) occured - */ -bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, const(char)* msg, - RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) -{ - //printf("setUnsafePreview() fs:%d %s\n", fs, msg); - with (FeatureState) final switch (fs) - { - case disabled: - return false; - - case enabled: - return sc.setUnsafe(gag, loc, msg, arg0, arg1, arg2); - - case default_: - if (!sc.func) - return false; - if (sc.func.isSafeBypassingInference()) - { - if (!gag && !sc.isDeprecated()) - { - deprecation(loc, msg, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); - } - } - else if (!sc.func.safetyViolation) - { - import dmd.func : AttributeViolation; - sc.func.safetyViolation = new AttributeViolation(loc, msg, arg0, arg1, arg2); - } - return false; - } -} - /// Stores a reason why a function failed to infer a function attribute like `@safe` or `pure` /// /// Has two modes: diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index 2cadc4019110..de9c68316b8d 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -1891,6 +1891,308 @@ Expression addInvariant(AggregateDeclaration ad, VarDeclaration vthis) return e; } +/******************************************** + * Find function in overload list that exactly matches t. + */ +FuncDeclaration overloadExactMatch(FuncDeclaration thisfd, Type t) +{ + FuncDeclaration fd; + overloadApply(thisfd, (Dsymbol s) + { + auto f = s.isFuncDeclaration(); + if (!f) + return 0; + if (f.storage_class & STC.disable) + return 0; + if (t.equals(f.type)) + { + fd = f; + return 1; + } + /* Allow covariant matches, as long as the return type + * is just a const conversion. + * This allows things like pure functions to match with an impure function type. + */ + if (t.ty == Tfunction) + { + auto tf = cast(TypeFunction)f.type; + if (tf.covariant(t) == Covariant.yes && + tf.nextOf().implicitConvTo(t.nextOf()) >= MATCH.constant) + { + fd = f; + return 1; + } + } + return 0; + }); + return fd; +} + +/******************************************** + * Find function in overload list that matches to the 'this' modifier. + * There's four result types. + * + * 1. If the 'tthis' matches only one candidate, it's an "exact match". + * Returns the function and 'hasOverloads' is set to false. + * eg. If 'tthis" is mutable and there's only one mutable method. + * 2. If there's two or more match candidates, but a candidate function will be + * a "better match". + * Returns the better match function but 'hasOverloads' is set to true. + * eg. If 'tthis' is mutable, and there's both mutable and const methods, + * the mutable method will be a better match. + * 3. If there's two or more match candidates, but there's no better match, + * Returns null and 'hasOverloads' is set to true to represent "ambiguous match". + * eg. If 'tthis' is mutable, and there's two or more mutable methods. + * 4. If there's no candidates, it's "no match" and returns null with error report. + * e.g. If 'tthis' is const but there's no const methods. + */ +FuncDeclaration overloadModMatch(FuncDeclaration thisfd, const ref Loc loc, Type tthis, ref bool hasOverloads) +{ + //printf("FuncDeclaration::overloadModMatch('%s')\n", toChars()); + MatchAccumulator m; + overloadApply(thisfd, (Dsymbol s) + { + auto f = s.isFuncDeclaration(); + if (!f || f == m.lastf) // skip duplicates + return 0; + auto tf = f.type.toTypeFunction(); + //printf("tf = %s\n", tf.toChars()); + MATCH match; + if (tthis) // non-static functions are preferred than static ones + { + if (f.needThis()) + match = f.isCtorDeclaration() ? MATCH.exact : MODmethodConv(tthis.mod, tf.mod); + else + match = MATCH.constant; // keep static function in overload candidates + } + else // static functions are preferred than non-static ones + { + if (f.needThis()) + match = MATCH.convert; + else + match = MATCH.exact; + } + if (match == MATCH.nomatch) + return 0; + if (match > m.last) goto LcurrIsBetter; + if (match < m.last) goto LlastIsBetter; + // See if one of the matches overrides the other. + if (m.lastf.overrides(f)) goto LlastIsBetter; + if (f.overrides(m.lastf)) goto LcurrIsBetter; + //printf("\tambiguous\n"); + m.nextf = f; + m.count++; + return 0; + LlastIsBetter: + //printf("\tlastbetter\n"); + m.count++; // count up + return 0; + LcurrIsBetter: + //printf("\tisbetter\n"); + if (m.last <= MATCH.convert) + { + // clear last secondary matching + m.nextf = null; + m.count = 0; + } + m.last = match; + m.lastf = f; + m.count++; // count up + return 0; + }); + if (m.count == 1) // exact match + { + hasOverloads = false; + } + else if (m.count > 1) // better or ambiguous match + { + hasOverloads = true; + } + else // no match + { + hasOverloads = true; + auto tf = thisfd.type.toTypeFunction(); + assert(tthis); + assert(!MODimplicitConv(tthis.mod, tf.mod)); // modifier mismatch + { + OutBuffer thisBuf, funcBuf; + MODMatchToBuffer(&thisBuf, tthis.mod, tf.mod); + MODMatchToBuffer(&funcBuf, tf.mod, tthis.mod); + .error(loc, "%smethod %s is not callable using a %sobject", thisfd.kind, thisfd.toPrettyChars, + funcBuf.peekChars(), thisfd.toPrettyChars(), thisBuf.peekChars()); + } + } + return m.lastf; +} + +/*********************************** + * Determine lexical level difference from `fd` to nested function `target`. + * Issue error if `fd` cannot call `target`. + * + * Params: + * loc = location for error messages + * sc = context + * target = target of call + * decl = The `Declaration` that triggered this check. + * Used to provide a better error message only. + * Returns: + * 0 same level + * >0 decrease nesting by number + * -1 increase nesting by 1 (`target` is nested within 'fd') + * LevelError error + */ +int getLevelAndCheck(FuncDeclaration fd, const ref Loc loc, Scope* sc, FuncDeclaration target, + Declaration decl) +{ + int level = fd.getLevel(target, sc.intypeof); + if (level != fd.LevelError) + return level; + // Don't give error if in template constraint + if (!(sc.flags & SCOPE.constraint)) + { + const(char)* xstatic = fd.isStatic() ? "`static` " : ""; + // better diagnostics for static functions + .error(loc, "%s%s `%s` cannot access %s `%s` in frame of function `%s`", + xstatic, fd.kind(), fd.toPrettyChars(), decl.kind(), decl.toChars(), + target.toPrettyChars()); + .errorSupplemental(decl.loc, "`%s` declared here", decl.toChars()); + return fd.LevelError; + } + return 1; +} + +/********************************** + * Decide if attributes for this function can be inferred from examining + * the function body. + * Returns: + * true if can + */ +bool canInferAttributes(FuncDeclaration fd, Scope* sc) +{ + if (!fd.fbody) + return false; + if (fd.isVirtualMethod() && + /* + * https://issues.dlang.org/show_bug.cgi?id=21719 + * + * If we have an auto virtual function we can infer + * the attributes. + */ + !(fd.inferRetType && !fd.isCtorDeclaration())) + return false; // since they may be overridden + if (sc.func && + /********** this is for backwards compatibility for the moment ********/ + (!fd.isMember() || sc.func.isSafeBypassingInference() && !fd.isInstantiated())) + return true; + if (fd.isFuncLiteralDeclaration() || // externs are not possible with literals + (fd.storage_class & STC.inference) || // do attribute inference + (fd.inferRetType && !fd.isCtorDeclaration())) + return true; + if (fd.isInstantiated()) + { + auto ti = fd.parent.isTemplateInstance(); + if (ti is null || ti.isTemplateMixin() || ti.tempdecl.ident == fd.ident) + return true; + } + return false; +} + +/********************************************* + * In the current function, we are calling 'this' function. + * 1. Check to see if the current function can call 'this' function, issue error if not. + * 2. If the current function is not the parent of 'this' function, then add + * the current function to the list of siblings of 'this' function. + * 3. If the current function is a literal, and it's accessing an uplevel scope, + * then mark it as a delegate. + * Returns true if error occurs. + */ +bool checkNestedReference(FuncDeclaration fd, Scope* sc, const ref Loc loc) +{ + //printf("FuncDeclaration::checkNestedReference() %s\n", toPrettyChars()); + if (auto fld = fd.isFuncLiteralDeclaration()) + { + if (fld.tok == TOK.reserved) + { + fld.tok = TOK.function_; + fld.vthis = null; + } + } + if (!fd.parent || fd.parent == sc.parent) + return false; + if (fd.ident == Id.require || fd.ident == Id.ensure) + return false; + if (!fd.isThis() && !fd.isNested()) + return false; + // The current function + FuncDeclaration fdthis = sc.parent.isFuncDeclaration(); + if (!fdthis) + return false; // out of function scope + Dsymbol p = fd.toParentLocal(); + Dsymbol p2 = fd.toParent2(); + // Function literals from fdthis to p must be delegates + ensureStaticLinkTo(fdthis, p); + if (p != p2) + ensureStaticLinkTo(fdthis, p2); + if (fd.isNested()) + { + // The function that this function is in + bool checkEnclosing(FuncDeclaration fdv) + { + if (!fdv) + return false; + if (fdv == fdthis) + return false; + //printf("this = %s in [%s]\n", this.toChars(), this.loc.toChars()); + //printf("fdv = %s in [%s]\n", fdv .toChars(), fdv .loc.toChars()); + //printf("fdthis = %s in [%s]\n", fdthis.toChars(), fdthis.loc.toChars()); + // Add this function to the list of those which called us + if (fdthis != fd) + { + bool found = false; + for (size_t i = 0; i < fd.siblingCallers.length; ++i) + { + if (fd.siblingCallers[i] == fdthis) + found = true; + } + if (!found) + { + //printf("\tadding sibling %s to %s\n", fdthis.toPrettyChars(), toPrettyChars()); + if (!sc.intypeof && !(sc.flags & SCOPE.compile)) + { + fd.siblingCallers.push(fdthis); + fd.computedEscapingSiblings = false; + } + } + } + const lv = fdthis.getLevelAndCheck(loc, sc, fdv, fd); + if (lv == fd.LevelError) + return true; // error + if (lv == -1) + return false; // downlevel call + if (lv == 0) + return false; // same level call + return false; // Uplevel call + } + if (checkEnclosing(p.isFuncDeclaration())) + return true; + if (checkEnclosing(p == p2 ? null : p2.isFuncDeclaration())) + return true; + } + return false; +} + +/**************************************************** + * Check whether result variable can be built. + * Returns: + * `true` if the function has a return type that + * is different from `void`. + */ +private bool canBuildResultVar(FuncDeclaration fd) +{ + auto f = cast(TypeFunction)fd.type; + return f && f.nextOf() && f.nextOf().toBasetype().ty != Tvoid; +} + /**************************************************** * Declare result variable lazily. */ @@ -1923,3 +2225,512 @@ void buildResultVar(FuncDeclaration fd, Scope* sc, Type tret) assert(fd.vresult.parent == fd); } } + +/**************************************************** + * Merge into this function the 'in' contracts of all it overrides. + * 'in's are OR'd together, i.e. only one of them needs to pass. + */ +Statement mergeFrequire(FuncDeclaration fd, Statement sf, Expressions* params) +{ + /* If a base function and its override both have an IN contract, then + * only one of them needs to succeed. This is done by generating: + * + * void derived.in() { + * try { + * base.in(); + * } + * catch () { + * ... body of derived.in() ... + * } + * } + * + * So if base.in() doesn't throw, derived.in() need not be executed, and the contract is valid. + * If base.in() throws, then derived.in()'s body is executed. + */ + foreach (fdv; fd.foverrides) + { + /* The semantic pass on the contracts of the overridden functions must + * be completed before code generation occurs. + * https://issues.dlang.org/show_bug.cgi?id=3602 + */ + if (fdv.frequires && fdv.semanticRun != PASS.semantic3done) + { + assert(fdv._scope); + Scope* sc = fdv._scope.push(); + sc.stc &= ~STC.override_; + fdv.semantic3(sc); + sc.pop(); + } + sf = fdv.mergeFrequire(sf, params); + if (!sf || !fdv.fdrequire) + return null; + //printf("fdv.frequire: %s\n", fdv.frequire.toChars()); + /* Make the call: + * try { __require(params); } + * catch (Throwable) { frequire; } + */ + params = Expression.arraySyntaxCopy(params); + Expression e = new CallExp(fd.loc, new VarExp(fd.loc, fdv.fdrequire, false), params); + Statement s2 = new ExpStatement(fd.loc, e); + auto c = new Catch(fd.loc, getThrowable(), null, sf); + c.internalCatch = true; + auto catches = new Catches(); + catches.push(c); + sf = new TryCatchStatement(fd.loc, s2, catches); + } + return sf; +} + +/**************************************************** + * Merge into this function the 'in' contracts of all it overrides. + */ +Statement mergeFrequireInclusivePreview(FuncDeclaration fd, Statement sf, Expressions* params) +{ + /* If a base function and its override both have an IN contract, then + * the override in contract must widen the guarantee of the base contract. + * This is checked by generating: + * + * void derived.in() { + * try { + * ... body of derived.in() ... + * } + * catch () { + * // derived in rejected this argument. so parent must also reject it, or we've tightened the contract. + * base.in(); + * assert(false, "Logic error: " ~ thr.msg); + * } + * } + */ + foreach (fdv; fd.foverrides) + { + /* The semantic pass on the contracts of the overridden functions must + * be completed before code generation occurs. + * https://issues.dlang.org/show_bug.cgi?id=3602 + */ + if (fdv.frequires && fdv.semanticRun != PASS.semantic3done) + { + assert(fdv._scope); + Scope* sc = fdv._scope.push(); + sc.stc &= ~STC.override_; + fdv.semantic3(sc); + sc.pop(); + } + sf = fdv.mergeFrequireInclusivePreview(sf, params); + if (sf && fdv.fdrequire) + { + const loc = fd.fdrequire.loc; + //printf("fdv.frequire: %s\n", fdv.frequire.toChars()); + /* Make the call: + * try { frequire; } + * catch (Throwable thr) { __require(params); assert(false, "Logic error: " ~ thr.msg); } + */ + Identifier id = Identifier.generateId("thr"); + params = Expression.arraySyntaxCopy(params); + Expression e = new CallExp(loc, new VarExp(loc, fdv.fdrequire, false), params); + Statement s2 = new ExpStatement(loc, e); + // assert(false, ...) + // TODO make this a runtime helper to allow: + // - chaining the original expression + // - nogc concatenation + Expression msg = new StringExp(loc, "Logic error: in-contract was tighter than parent in-contract"); + Statement fail = new ExpStatement(loc, new AssertExp(loc, IntegerExp.literal!0, msg)); + Statement s3 = new CompoundStatement(loc, s2, fail); + auto c = new Catch(loc, getThrowable(), id, s3); + c.internalCatch = true; + auto catches = new Catches(); + catches.push(c); + sf = new TryCatchStatement(loc, sf, catches); + } + else + return null; + } + return sf; +} + +/**************************************************** + * Rewrite contracts as statements. + */ +void buildEnsureRequire(FuncDeclaration thisfd) +{ + if (thisfd.frequires) + { + /* in { statements1... } + * in { statements2... } + * ... + * becomes: + * in { { statements1... } { statements2... } ... } + */ + assert(thisfd.frequires.length); + auto loc = (*thisfd.frequires)[0].loc; + auto s = new Statements; + foreach (r; *thisfd.frequires) + { + s.push(new ScopeStatement(r.loc, r, r.loc)); + } + thisfd.frequire = new CompoundStatement(loc, s); + } + if (thisfd.fensures) + { + /* out(id1) { statements1... } + * out(id2) { statements2... } + * ... + * becomes: + * out(__result) { { ref id1 = __result; { statements1... } } + * { ref id2 = __result; { statements2... } } ... } + */ + assert(thisfd.fensures.length); + auto loc = (*thisfd.fensures)[0].ensure.loc; + auto s = new Statements; + foreach (r; *thisfd.fensures) + { + if (r.id && thisfd.canBuildResultVar()) + { + auto rloc = r.ensure.loc; + auto resultId = new IdentifierExp(rloc, Id.result); + auto init = new ExpInitializer(rloc, resultId); + auto stc = STC.ref_ | STC.temp | STC.result; + auto decl = new VarDeclaration(rloc, null, r.id, init, stc); + auto sdecl = new ExpStatement(rloc, decl); + s.push(new ScopeStatement(rloc, new CompoundStatement(rloc, sdecl, r.ensure), rloc)); + } + else + { + s.push(r.ensure); + } + } + thisfd.fensure = new CompoundStatement(loc, s); + } + if (!thisfd.isVirtual()) + return; + /* Rewrite contracts as nested functions, then call them. Doing it as nested + * functions means that overriding functions can call them. + */ + TypeFunction f = cast(TypeFunction) thisfd.type; + /* Make a copy of the parameters and make them all ref */ + static Parameters* toRefCopy(ParameterList parameterList) + { + auto result = new Parameters(); + foreach (n, p; parameterList) + { + p = p.syntaxCopy(); + if (!p.isLazy()) + p.storageClass = (p.storageClass | STC.ref_) & ~STC.out_; + p.defaultArg = null; // won't be the same with ref + result.push(p); + } + return result; + } + if (thisfd.frequire) + { + /* in { ... } + * becomes: + * void __require(ref params) { ... } + * __require(params); + */ + Loc loc = thisfd.frequire.loc; + thisfd.fdrequireParams = new Expressions(); + if (thisfd.parameters) + { + foreach (vd; *thisfd.parameters) + thisfd.fdrequireParams.push(new VarExp(loc, vd)); + } + auto fo = cast(TypeFunction)(thisfd.originalType ? thisfd.originalType : f); + auto fparams = toRefCopy(fo.parameterList); + auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d); + tf.isnothrow = f.isnothrow; + tf.isnogc = f.isnogc; + tf.purity = f.purity; + tf.trust = f.trust; + auto fd = new FuncDeclaration(loc, loc, Id.require, STC.undefined_, tf); + fd.fbody = thisfd.frequire; + Statement s1 = new ExpStatement(loc, fd); + Expression e = new CallExp(loc, new VarExp(loc, fd, false), thisfd.fdrequireParams); + Statement s2 = new ExpStatement(loc, e); + thisfd.frequire = new CompoundStatement(loc, s1, s2); + thisfd.fdrequire = fd; + } + /* We need to set fdensureParams here and not in the block below to + * have the parameters available when calling a base class ensure(), + * even if this function doesn't have an out contract. + */ + thisfd.fdensureParams = new Expressions(); + if (thisfd.canBuildResultVar()) + thisfd.fdensureParams.push(new IdentifierExp(thisfd.loc, Id.result)); + if (thisfd.parameters) + { + foreach (vd; *thisfd.parameters) + thisfd.fdensureParams.push(new VarExp(thisfd.loc, vd)); + } + if (thisfd.fensure) + { + /* out (result) { ... } + * becomes: + * void __ensure(ref tret result, ref params) { ... } + * __ensure(result, params); + */ + Loc loc = thisfd.fensure.loc; + auto fparams = new Parameters(); + if (thisfd.canBuildResultVar()) + { + Parameter p = new Parameter(loc, STC.ref_ | STC.const_, f.nextOf(), Id.result, null, null); + fparams.push(p); + } + auto fo = cast(TypeFunction)(thisfd.originalType ? thisfd.originalType : f); + fparams.pushSlice((*toRefCopy(fo.parameterList))[]); + auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d); + tf.isnothrow = f.isnothrow; + tf.isnogc = f.isnogc; + tf.purity = f.purity; + tf.trust = f.trust; + auto fd = new FuncDeclaration(loc, loc, Id.ensure, STC.undefined_, tf); + fd.fbody = thisfd.fensure; + Statement s1 = new ExpStatement(loc, fd); + Expression e = new CallExp(loc, new VarExp(loc, fd, false), thisfd.fdensureParams); + Statement s2 = new ExpStatement(loc, e); + thisfd.fensure = new CompoundStatement(loc, s1, s2); + thisfd.fdensure = fd; + } +} + +/**************************************************** + * Merge into this function the 'out' contracts of all it overrides. + * 'out's are AND'd together, i.e. all of them need to pass. + */ +Statement mergeFensure(FuncDeclaration fd, Statement sf, Identifier oid, Expressions* params) +{ + /* Same comments as for mergeFrequire(), except that we take care + * of generating a consistent reference to the 'result' local by + * explicitly passing 'result' to the nested function as a reference + * argument. + * This won't work for the 'this' parameter as it would require changing + * the semantic code for the nested function so that it looks on the parameter + * list for the 'this' pointer, something that would need an unknown amount + * of tweaking of various parts of the compiler that I'd rather leave alone. + */ + foreach (fdv; fd.foverrides) + { + /* The semantic pass on the contracts of the overridden functions must + * be completed before code generation occurs. + * https://issues.dlang.org/show_bug.cgi?id=3602 and + * https://issues.dlang.org/show_bug.cgi?id=5230 + */ + if (fd.needsFensure(fdv) && fdv.semanticRun != PASS.semantic3done) + { + assert(fdv._scope); + Scope* sc = fdv._scope.push(); + sc.stc &= ~STC.override_; + fdv.semantic3(sc); + sc.pop(); + } + sf = fdv.mergeFensure(sf, oid, params); + if (fdv.fdensure) + { + //printf("fdv.fensure: %s\n", fdv.fensure.toChars()); + // Make the call: __ensure(result, params) + params = Expression.arraySyntaxCopy(params); + if (fd.canBuildResultVar()) + { + Type t1 = fdv.type.nextOf().toBasetype(); + Type t2 = fd.type.nextOf().toBasetype(); + if (t1.isBaseOf(t2, null)) + { + /* Making temporary reference variable is necessary + * in covariant return. + * https://issues.dlang.org/show_bug.cgi?id=5204 + * https://issues.dlang.org/show_bug.cgi?id=10479 + */ + Expression* eresult = &(*params)[0]; + auto ei = new ExpInitializer(Loc.initial, *eresult); + auto v = new VarDeclaration(Loc.initial, t1, Identifier.generateId("__covres"), ei); + v.storage_class |= STC.temp; + auto de = new DeclarationExp(Loc.initial, v); + auto ve = new VarExp(Loc.initial, v); + *eresult = new CommaExp(Loc.initial, de, ve); + } + } + Expression e = new CallExp(fd.loc, new VarExp(fd.loc, fdv.fdensure, false), params); + Statement s2 = new ExpStatement(fd.loc, e); + if (sf) + { + sf = new CompoundStatement(sf.loc, s2, sf); + } + else + sf = s2; + } + } + return sf; +} + +/******************************* + * Modify all expression type of return statements to tret. + * + * On function literals, return type may be modified based on the context type + * after its semantic3 is done, in FuncExp::implicitCastTo. + * + * A function() dg = (){ return new B(); } // OK if is(B : A) == true + * + * If B to A conversion is convariant that requires offseet adjusting, + * all return statements should be adjusted to return expressions typed A. + */ +void modifyReturns(FuncLiteralDeclaration fld, Scope* sc, Type tret) +{ + import dmd.statement_rewrite_walker; + extern (C++) final class RetWalker : StatementRewriteWalker + { + alias visit = typeof(super).visit; + public: + Scope* sc; + Type tret; + FuncLiteralDeclaration fld; + override void visit(ReturnStatement s) + { + Expression exp = s.exp; + if (exp && !exp.type.equals(tret)) + s.exp = exp.implicitCastTo(sc, tret); + } + } + if (fld.semanticRun < PASS.semantic3done) + return; + if (fld.fes) + return; + scope RetWalker w = new RetWalker(); + w.sc = sc; + w.tret = tret; + w.fld = fld; + fld.fbody.accept(w); + // Also update the inferred function type to match the new return type. + // This is required so the code generator does not try to cast the + // modified returns back to the original type. + if (fld.inferRetType && fld.type.nextOf() != tret) + fld.type.toTypeFunction().next = tret; +} + +/************************************** + * When a traits(compiles) is used on a function literal call + * we need to take into account if the body of the function + * violates any attributes, however, we must not affect the + * attribute inference on the outer function. The attributes + * of the function literal still need to be inferred, therefore + * we need a way to check for the scope that the traits compiles + * introduces. + * + * Params: + * sc = scope to be checked for + * + * Returns: `true` if the provided scope is the root + * of the traits compiles list of scopes. + */ +bool isRootTraitsCompilesScope(Scope* sc) +{ + return (sc.flags & SCOPE.compile) && !(sc.func.flags & SCOPE.compile); +} + +/************************************** + * A statement / expression in this scope is not `@safe`, + * so mark the enclosing function as `@system` + * + * Params: + * sc = scope that the unsafe statement / expression is in + * gag = surpress error message (used in escape.d) + * loc = location of error + * fmt = printf-style format string + * arg0 = (optional) argument for first %s format specifier + * arg1 = (optional) argument for second %s format specifier + * arg2 = (optional) argument for third %s format specifier + * Returns: whether there's a safe error + */ +bool setUnsafe(Scope* sc, + bool gag = false, Loc loc = Loc.init, const(char)* fmt = null, + RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) +{ + if (sc.intypeof) + return false; // typeof(cast(int*)0) is safe + + if (sc.flags & SCOPE.debug_) // debug {} scopes are permissive + return false; + + if (!sc.func) + { + if (sc.varDecl) + { + if (sc.varDecl.storage_class & STC.safe) + { + .error(loc, fmt, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); + return true; + } + else if (!(sc.varDecl.storage_class & STC.trusted)) + { + sc.varDecl.storage_class |= STC.system; + sc.varDecl.systemInferred = true; + } + } + return false; + } + + + if (isRootTraitsCompilesScope(sc)) // __traits(compiles, x) + { + if (sc.func.isSafeBypassingInference()) + { + // Message wil be gagged, but still call error() to update global.errors and for + // -verrors=spec + .error(loc, fmt, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); + return true; + } + return false; + } + + return sc.func.setUnsafe(gag, loc, fmt, arg0, arg1, arg2); +} + +/*************************************** + * Like `setUnsafe`, but for safety errors still behind preview switches + * + * Given a `FeatureState fs`, for example dip1000 / dip25 / systemVariables, + * the behavior changes based on the setting: + * + * - In case of `-revert=fs`, it does nothing. + * - In case of `-preview=fs`, it's the same as `setUnsafe` + * - By default, print a deprecation in `@safe` functions, or store an attribute violation in inferred functions. + * + * Params: + * sc = used to find affected function/variable, and for checking whether we are in a deprecated / speculative scope + * fs = feature state from the preview flag + * gag = surpress error message + * loc = location of error + * msg = printf-style format string + * arg0 = (optional) argument for first %s format specifier + * arg1 = (optional) argument for second %s format specifier + * arg2 = (optional) argument for third %s format specifier + * Returns: whether an actual safe error (not deprecation) occured + */ +bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, const(char)* msg, + RootObject arg0 = null, RootObject arg1 = null, RootObject arg2 = null) +{ + //printf("setUnsafePreview() fs:%d %s\n", fs, msg); + with (FeatureState) final switch (fs) + { + case disabled: + return false; + + case enabled: + return sc.setUnsafe(gag, loc, msg, arg0, arg1, arg2); + + case default_: + if (!sc.func) + return false; + if (sc.func.isSafeBypassingInference()) + { + if (!gag && !sc.isDeprecated()) + { + deprecation(loc, msg, arg0 ? arg0.toChars() : "", arg1 ? arg1.toChars() : "", arg2 ? arg2.toChars() : ""); + } + } + else if (!sc.func.safetyViolation) + { + import dmd.func : AttributeViolation; + sc.func.safetyViolation = new AttributeViolation(loc, msg, arg0, arg1, arg2); + } + return false; + } +} diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 70eeaff7f47e..0d32d7d9713d 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -29,6 +29,7 @@ import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; +import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; diff --git a/compiler/src/dmd/safe.d b/compiler/src/dmd/safe.d index 1e5fb47a3dc7..741f979184ee 100644 --- a/compiler/src/dmd/safe.d +++ b/compiler/src/dmd/safe.d @@ -27,7 +27,7 @@ import dmd.mtype; import dmd.target; import dmd.tokens; import dmd.typesem : hasPointers, arrayOf; -import dmd.func : setUnsafe, setUnsafePreview; +import dmd.funcsem : setUnsafe, setUnsafePreview; /************************************************************* * Check for unsafe access in @safe code: diff --git a/compiler/src/dmd/semantic2.d b/compiler/src/dmd/semantic2.d index f5ce0c0ada63..4bb39028f041 100644 --- a/compiler/src/dmd/semantic2.d +++ b/compiler/src/dmd/semantic2.d @@ -40,6 +40,7 @@ import dmd.escape; import dmd.expression; import dmd.expressionsem; import dmd.func; +import dmd.funcsem; import dmd.globals; import dmd.id; import dmd.identifier;