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