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 }