1 /** 2 * Implements the `alias this` symbol. 3 * 4 * Specification: $(LINK2 https://dlang.org/spec/class.html#alias-this, Alias This) 5 * 6 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved 7 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 8 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/aliasthis.d, _aliasthis.d) 10 * Documentation: https://dlang.org/phobos/dmd_aliasthis.html 11 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/aliasthis.d 12 */ 13 14 module dmd.aliasthis; 15 16 import core.stdc.stdio; 17 import dmd.aggregate; 18 import dmd.dscope; 19 import dmd.dsymbol; 20 import dmd.expression; 21 import dmd.expressionsem; 22 import dmd.globals; 23 import dmd.identifier; 24 import dmd.location; 25 import dmd.mtype; 26 import dmd.opover; 27 import dmd.tokens; 28 import dmd.visitor; 29 30 /*********************************************************** 31 * alias ident this; 32 */ 33 extern (C++) final class AliasThis : Dsymbol 34 { 35 Identifier ident; 36 /// The symbol this `alias this` resolves to 37 Dsymbol sym; 38 /// Whether this `alias this` is deprecated or not 39 bool isDeprecated_; 40 41 extern (D) this(const ref Loc loc, Identifier ident) 42 { 43 super(loc, null); // it's anonymous (no identifier) 44 this.ident = ident; 45 } 46 47 override AliasThis syntaxCopy(Dsymbol s) 48 { 49 assert(!s); 50 auto at = new AliasThis(loc, ident); 51 at.comment = comment; 52 return at; 53 } 54 55 override const(char)* kind() const 56 { 57 return "alias this"; 58 } 59 60 AliasThis isAliasThis() 61 { 62 return this; 63 } 64 65 override void accept(Visitor v) 66 { 67 v.visit(this); 68 } 69 70 override bool isDeprecated() const 71 { 72 return this.isDeprecated_; 73 } 74 } 75 76 /************************************* 77 * Find the `alias this` symbol of e's type. 78 * Params: 79 * sc = context 80 * e = expression forming the `this` 81 * gag = do not print errors, return `null` instead 82 * findOnly = don't do further processing like resolving properties, 83 * i.e. just return plain dotExp() result. 84 * Returns: 85 * Expression that is `e.aliasthis` 86 */ 87 Expression resolveAliasThis(Scope* sc, Expression e, bool gag = false, bool findOnly = false) 88 { 89 import dmd.typesem : dotExp; 90 for (AggregateDeclaration ad = isAggregate(e.type); ad;) 91 { 92 if (ad.aliasthis) 93 { 94 Loc loc = e.loc; 95 Type tthis = (e.op == EXP.type ? e.type : null); 96 const flags = cast(DotExpFlag) (DotExpFlag.noAliasThis | (gag * DotExpFlag.gag)); 97 uint olderrors = gag ? global.startGagging() : 0; 98 e = dotExp(ad.type, sc, e, ad.aliasthis.ident, flags); 99 if (!e || findOnly) 100 return gag && global.endGagging(olderrors) ? null : e; 101 102 if (tthis && ad.aliasthis.sym.needThis()) 103 { 104 if (auto ve = e.isVarExp()) 105 { 106 if (auto fd = ve.var.isFuncDeclaration()) 107 { 108 // https://issues.dlang.org/show_bug.cgi?id=13009 109 // Support better match for the overloaded alias this. 110 bool hasOverloads; 111 if (auto f = fd.overloadModMatch(loc, tthis, hasOverloads)) 112 { 113 if (!hasOverloads) 114 fd = f; // use exact match 115 e = new VarExp(loc, fd, hasOverloads); 116 e.type = f.type; 117 e = new CallExp(loc, e); 118 goto L1; 119 } 120 } 121 } 122 /* non-@property function is not called inside typeof(), 123 * so resolve it ahead. 124 */ 125 { 126 int save = sc.intypeof; 127 sc.intypeof = 1; // bypass "need this" error check 128 e = resolveProperties(sc, e); 129 sc.intypeof = save; 130 } 131 L1: 132 e = new TypeExp(loc, new TypeTypeof(loc, e)); 133 e = e.expressionSemantic(sc); 134 } 135 e = resolveProperties(sc, e); 136 if (!gag) 137 ad.aliasthis.checkDeprecatedAliasThis(loc, sc); 138 else if (global.endGagging(olderrors)) 139 e = null; 140 } 141 142 import dmd.dclass : ClassDeclaration; 143 auto cd = ad.isClassDeclaration(); 144 if ((!e || !ad.aliasthis) && cd && cd.baseClass && cd.baseClass != ClassDeclaration.object) 145 { 146 ad = cd.baseClass; 147 continue; 148 } 149 break; 150 } 151 return e; 152 } 153 154 /** 155 * Check if an `alias this` is deprecated 156 * 157 * Usually one would use `expression.checkDeprecated(scope, aliasthis)` to 158 * check if `expression` uses a deprecated `aliasthis`, but this calls 159 * `toPrettyChars` which lead to the following message: 160 * "Deprecation: alias this `fullyqualified.aggregate.__anonymous` is deprecated" 161 * 162 * Params: 163 * at = The `AliasThis` object to check 164 * loc = `Loc` of the expression triggering the access to `at` 165 * sc = `Scope` of the expression 166 * (deprecations do not trigger in deprecated scopes) 167 * 168 * Returns: 169 * Whether the alias this was reported as deprecated. 170 */ 171 bool checkDeprecatedAliasThis(AliasThis at, const ref Loc loc, Scope* sc) 172 { 173 import dmd.errors : deprecation, Classification; 174 import dmd.dsymbolsem : getMessage; 175 176 if (global.params.useDeprecated != DiagnosticReporting.off 177 && at.isDeprecated() && !sc.isDeprecated()) 178 { 179 const(char)* message = null; 180 for (Dsymbol p = at; p; p = p.parent) 181 { 182 message = p.depdecl ? p.depdecl.getMessage() : null; 183 if (message) 184 break; 185 } 186 if (message) 187 deprecation(loc, "`alias %s this` is deprecated - %s", 188 at.sym.toChars(), message); 189 else 190 deprecation(loc, "`alias %s this` is deprecated", 191 at.sym.toChars()); 192 193 if (auto ti = sc.parent ? sc.parent.isInstantiated() : null) 194 ti.printInstantiationTrace(Classification.deprecation); 195 196 return true; 197 } 198 return false; 199 } 200 201 /************************************** 202 * Check and set 'att' if 't' is a recursive 'alias this' type 203 * 204 * The goal is to prevent endless loops when there is a cycle in the alias this chain. 205 * Since there is no multiple `alias this`, the chain either ends in a leaf, 206 * or it loops back on itself as some point. 207 * 208 * Example: S0 -> (S1 -> S2 -> S3 -> S1) 209 * 210 * `S0` is not a recursive alias this, so this returns `false`, and a rewrite to `S1` can be tried. 211 * `S1` is a recursive alias this type, but since `att` is initialized to `null`, 212 * this still returns `false`, but `att1` is set to `S1`. 213 * A rewrite to `S2` and `S3` can be tried, but when we want to try a rewrite to `S1` again, 214 * we notice `att == t`, so we're back at the start of the loop, and this returns `true`. 215 * 216 * Params: 217 * att = type reference used to detect recursion. Should be initialized to `null`. 218 * t = type of 'alias this' rewrite to attempt 219 * 220 * Returns: 221 * `false` if the rewrite is safe, `true` if it would loop back around 222 */ 223 bool isRecursiveAliasThis(ref Type att, Type t) 224 { 225 //printf("+isRecursiveAliasThis(att = %s, t = %s)\n", att ? att.toChars() : "null", t.toChars()); 226 auto tb = t.toBasetype(); 227 if (att && tb.equivalent(att)) 228 return true; 229 else if (!att && tb.checkAliasThisRec()) 230 att = tb; 231 return false; 232 }