1 /**
2  * Implements the serialization of a lambda function.
3  *
4  * The serializationis computed by visiting the abstract syntax subtree of the given lambda function.
5  * The serialization is a string which contains the type of the parameters and the string
6  * represantation of the lambda expression.
7  *
8  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
9  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
10  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
11  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/lamdbacomp.d, _lambdacomp.d)
12  * Documentation:  https://dlang.org/phobos/dmd_lambdacomp.html
13  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/lambdacomp.d
14  */
15 
16 module dmd.lambdacomp;
17 
18 import core.stdc.stdio;
19 import core.stdc.string;
20 
21 import dmd.astenums;
22 import dmd.declaration;
23 import dmd.denum;
24 import dmd.dsymbol;
25 import dmd.dtemplate;
26 import dmd.expression;
27 import dmd.func;
28 import dmd.dmangle;
29 import dmd.hdrgen;
30 import dmd.mtype;
31 import dmd.common.outbuffer;
32 import dmd.root.rmem;
33 import dmd.root.stringtable;
34 import dmd.dscope;
35 import dmd.statement;
36 import dmd.tokens;
37 import dmd.visitor;
38 
39 enum LOG = false;
40 
41 /**
42  * The type of the visited expression.
43  */
44 private enum ExpType
45 {
46     None,
47     EnumDecl,
48     Arg
49 }
50 
51 /**
52  * Compares 2 lambda functions described by their serialization.
53  *
54  * Params:
55  *  l1 = first lambda to be compared
56  *  l2 = second lambda to be compared
57  *  sc = the scope where the lambdas are compared
58  *
59  * Returns:
60  *  `true` if the 2 lambda functions are equal, `false` otherwise
61  */
62 bool isSameFuncLiteral(FuncLiteralDeclaration l1, FuncLiteralDeclaration l2, Scope* sc)
63 {
64     bool result;
65     if (auto ser1 = getSerialization(l1, sc))
66     {
67         //printf("l1 serialization: %.*s\n", cast(int)ser1.length, &ser1[0]);
68         if (auto ser2 = getSerialization(l2, sc))
69         {
70             //printf("l2 serialization: %.*s\n", cast(int)ser2.length, &ser2[0]);
71             if (ser1 == ser2)
72                 result = true;
73             mem.xfree(cast(void*)ser2.ptr);
74         }
75         mem.xfree(cast(void*)ser1.ptr);
76     }
77     return result;
78 }
79 
80 /**
81  * Computes the string representation of a
82  * lambda function described by the subtree starting from a
83  * $(REF dmd, func, FuncLiteralDeclaration).
84  *
85  * Limitations: only IntegerExps, Enums and function
86  * arguments are supported in the lambda function body. The
87  * arguments may be of any type (basic types, user defined types),
88  * except template instantiations. If a function call, a local
89  * variable or a template instance is encountered, the
90  * serialization is dropped and the function is considered
91  * uncomparable.
92  *
93  * Params:
94  *  fld = the starting AST node for the lambda function
95  *  sc = the scope in which the lambda function is located
96  *
97  * Returns:
98  *  The serialization of `fld` allocated with mem.
99  */
100 private string getSerialization(FuncLiteralDeclaration fld, Scope* sc)
101 {
102     scope serVisitor = new SerializeVisitor(fld.parent._scope);
103     fld.accept(serVisitor);
104     const len = serVisitor.buf.length;
105     if (len == 0)
106         return null;
107 
108     return cast(string)serVisitor.buf.extractSlice();
109 }
110 
111 private extern (C++) class SerializeVisitor : SemanticTimeTransitiveVisitor
112 {
113 private:
114     StringTable!(const(char)[]) arg_hash;
115     Scope* sc;
116     ExpType et;
117     Dsymbol d;
118 
119 public:
120     OutBuffer buf;
121     alias visit = SemanticTimeTransitiveVisitor.visit;
122 
123     this(Scope* sc) scope
124     {
125         this.sc = sc;
126     }
127 
128     /**
129      * Entrypoint of the SerializeVisitor.
130      *
131      * Params:
132      *     fld = the lambda function for which the serialization is computed
133      */
134     override void visit(FuncLiteralDeclaration fld)
135     {
136         assert(fld.type.ty != Terror);
137         static if (LOG)
138             printf("FuncLiteralDeclaration: %s\n", fld.toChars());
139 
140         TypeFunction tf = cast(TypeFunction) fld.type;
141         const dim = cast(uint) tf.parameterList.length;
142         // Start the serialization by printing the number of
143         // arguments the lambda has.
144         buf.printf("%d:", dim);
145 
146         arg_hash._init(dim + 1);
147         // For each argument
148         foreach (i, fparam; tf.parameterList)
149         {
150             if (fparam.ident !is null)
151             {
152                 // the variable name is introduced into a hashtable
153                 // where the key is the user defined name and the
154                 // value is the cannonically name (arg0, arg1 ...)
155                 auto key = fparam.ident.toString();
156                 OutBuffer value;
157                 value.writestring("arg");
158                 value.print(i);
159                 arg_hash.insert(key, value.extractSlice());
160                 // and the type of the variable is serialized.
161                 fparam.accept(this);
162             }
163         }
164 
165         // Now the function body can be serialized.
166         ReturnStatement rs = fld.fbody.endsWithReturnStatement();
167         if (rs && rs.exp)
168         {
169             rs.exp.accept(this);
170         }
171         else
172         {
173             buf.setsize(0);
174         }
175     }
176 
177     override void visit(DotIdExp exp)
178     {
179         static if (LOG)
180             printf("DotIdExp: %s\n", exp.toChars());
181         if (buf.length == 0)
182             return;
183 
184         // First we need to see what kind of expression e1 is.
185         // It might an enum member (enum.value)  or the field of
186         // an argument (argX.value) if the argument is an aggregate
187         // type. This is reported through the et variable.
188         exp.e1.accept(this);
189         if (buf.length == 0)
190             return;
191 
192         if (et == ExpType.EnumDecl)
193         {
194             Dsymbol s = d.search(exp.loc, exp.ident);
195             if (s)
196             {
197                 if (auto em = s.isEnumMember())
198                 {
199                     em.value.accept(this);
200                 }
201                 et = ExpType.None;
202                 d = null;
203             }
204         }
205 
206         else if (et == ExpType.Arg)
207         {
208             buf.setsize(buf.length -1);
209             buf.writeByte('.');
210             buf.writestring(exp.ident.toString());
211             buf.writeByte('_');
212         }
213     }
214 
215     bool checkArgument(const(char)* id)
216     {
217         // The identifier may be an argument
218         auto stringtable_value = arg_hash.lookup(id, strlen(id));
219         if (stringtable_value)
220         {
221             // In which case we need to update the serialization accordingly
222             const(char)[] gen_id = stringtable_value.value;
223             buf.write(gen_id);
224             buf.writeByte('_');
225             et = ExpType.Arg;
226             return true;
227         }
228         return false;
229     }
230 
231     override void visit(IdentifierExp exp)
232     {
233         static if (LOG)
234             printf("IdentifierExp: %s\n", exp.toChars());
235 
236         if (buf.length == 0)
237             return;
238 
239         auto id = exp.ident.toChars();
240 
241         // If it's not an argument
242         if (!checkArgument(id))
243         {
244             // we must check what the identifier expression is.
245             Dsymbol scopesym;
246             Dsymbol s = sc.search(exp.loc, exp.ident, &scopesym);
247             if (s)
248             {
249                 auto v = s.isVarDeclaration();
250                 // If it's a VarDeclaration, it must be a manifest constant
251                 if (v && (v.storage_class & STC.manifest))
252                 {
253                     v.getConstInitializer.accept(this);
254                 }
255                 else if (auto em = s.isEnumDeclaration())
256                 {
257                     d = em;
258                     et = ExpType.EnumDecl;
259                 }
260                 else if (auto fd = s.isFuncDeclaration())
261                 {
262                     writeMangledName(fd);
263                 }
264                 // For anything else, the function is deemed uncomparable
265                 else
266                 {
267                     buf.setsize(0);
268                 }
269             }
270             // If it's an unknown symbol, consider the function incomparable
271             else
272             {
273                 buf.setsize(0);
274             }
275         }
276     }
277 
278     override void visit(DotVarExp exp)
279     {
280         static if (LOG)
281             printf("DotVarExp: %s, var: %s, e1: %s\n", exp.toChars(),
282                     exp.var.toChars(), exp.e1.toChars());
283 
284         exp.e1.accept(this);
285         if (buf.length == 0)
286             return;
287 
288         buf.setsize(buf.length -1);
289         buf.writeByte('.');
290         buf.writestring(exp.var.toChars());
291         buf.writeByte('_');
292     }
293 
294     override void visit(VarExp exp)
295     {
296         static if (LOG)
297             printf("VarExp: %s, var: %s\n", exp.toChars(), exp.var.toChars());
298 
299         if (buf.length == 0)
300             return;
301 
302         auto id = exp.var.ident.toChars();
303         if (!checkArgument(id))
304         {
305             buf.setsize(0);
306         }
307     }
308 
309     // serialize function calls
310     override void visit(CallExp exp)
311     {
312         static if (LOG)
313             printf("CallExp: %s\n", exp.toChars());
314 
315         if (buf.length == 0)
316             return;
317 
318         if (!exp.f)
319         {
320             exp.e1.accept(this);
321         }
322         else
323         {
324             writeMangledName(exp.f);
325         }
326 
327         buf.writeByte('(');
328         foreach (arg; *(exp.arguments))
329         {
330             arg.accept(this);
331         }
332         buf.writeByte(')');
333     }
334 
335     override void visit(UnaExp exp)
336     {
337         if (buf.length == 0)
338             return;
339 
340         buf.writeByte('(');
341         buf.writestring(EXPtoString(exp.op));
342         exp.e1.accept(this);
343         if (buf.length != 0)
344             buf.writestring(")_");
345     }
346 
347     override void visit(IntegerExp exp)
348     {
349         if (buf.length == 0)
350             return;
351 
352         buf.print(exp.toInteger());
353         buf.writeByte('_');
354     }
355 
356     override void visit(RealExp exp)
357     {
358         if (buf.length == 0)
359             return;
360 
361         buf.writestring(exp.toChars());
362         buf.writeByte('_');
363     }
364 
365     override void visit(BinExp exp)
366     {
367         static if (LOG)
368             printf("BinExp: %s\n", exp.toChars());
369 
370         if (buf.length == 0)
371             return;
372 
373         buf.writeByte('(');
374         buf.writestring(EXPtoString(exp.op).ptr);
375 
376         exp.e1.accept(this);
377         if (buf.length == 0)
378             return;
379 
380         exp.e2.accept(this);
381         if (buf.length == 0)
382             return;
383 
384         buf.writeByte(')');
385     }
386 
387     override void visit(TypeBasic t)
388     {
389         buf.writestring(t.dstring);
390         buf.writeByte('_');
391     }
392 
393     void writeMangledName(Dsymbol s)
394     {
395         if (s)
396         {
397             OutBuffer mangledName;
398             mangleToBuffer(s, mangledName);
399             buf.writestring(mangledName[]);
400             buf.writeByte('_');
401         }
402         else
403             buf.setsize(0);
404     }
405 
406     private bool checkTemplateInstance(T)(T t)
407         if (is(T == TypeStruct) || is(T == TypeClass))
408     {
409         if (t.sym.parent && t.sym.parent.isTemplateInstance())
410         {
411             buf.setsize(0);
412             return true;
413         }
414         return false;
415     }
416 
417     override void visit(TypeStruct t)
418     {
419         static if (LOG)
420             printf("TypeStruct: %s\n", t.toChars);
421 
422         if (!checkTemplateInstance!TypeStruct(t))
423             writeMangledName(t.sym);
424     }
425 
426     override void visit(TypeClass t)
427     {
428         static if (LOG)
429             printf("TypeClass: %s\n", t.toChars());
430 
431         if (!checkTemplateInstance!TypeClass(t))
432             writeMangledName(t.sym);
433     }
434 
435     override void visit(Parameter p)
436     {
437         if (p.type.ty == Tident
438             && (cast(TypeIdentifier)p.type).ident.toString().length > 3
439             && strncmp((cast(TypeIdentifier)p.type).ident.toChars(), "__T", 3) == 0)
440         {
441             buf.writestring("none_");
442         }
443         else
444             visitType(p.type);
445     }
446 
447     override void visit(StructLiteralExp e) {
448         static if (LOG)
449             printf("StructLiteralExp: %s\n", e.toChars);
450 
451         auto ty = cast(TypeStruct)e.stype;
452         if (ty)
453         {
454             writeMangledName(ty.sym);
455             auto dim = e.elements.length;
456             foreach (i; 0..dim)
457             {
458                 auto elem = (*e.elements)[i];
459                 if (elem)
460                     elem.accept(this);
461                 else
462                     buf.writestring("null_");
463             }
464         }
465         else
466             buf.setsize(0);
467     }
468 
469     override void visit(ArrayLiteralExp) { buf.setsize(0); }
470     override void visit(AssocArrayLiteralExp) { buf.setsize(0); }
471     override void visit(MixinExp) { buf.setsize(0); }
472     override void visit(ComplexExp) { buf.setsize(0); }
473     override void visit(DeclarationExp) { buf.setsize(0); }
474     override void visit(DefaultInitExp) { buf.setsize(0); }
475     override void visit(DsymbolExp) { buf.setsize(0); }
476     override void visit(ErrorExp) { buf.setsize(0); }
477     override void visit(FuncExp) { buf.setsize(0); }
478     override void visit(HaltExp) { buf.setsize(0); }
479     override void visit(IntervalExp) { buf.setsize(0); }
480     override void visit(IsExp) { buf.setsize(0); }
481     override void visit(NewAnonClassExp) { buf.setsize(0); }
482     override void visit(NewExp) { buf.setsize(0); }
483     override void visit(NullExp) { buf.setsize(0); }
484     override void visit(ObjcClassReferenceExp) { buf.setsize(0); }
485     override void visit(OverExp) { buf.setsize(0); }
486     override void visit(ScopeExp) { buf.setsize(0); }
487     override void visit(StringExp) { buf.setsize(0); }
488     override void visit(SymbolExp) { buf.setsize(0); }
489     override void visit(TemplateExp) { buf.setsize(0); }
490     override void visit(ThisExp) { buf.setsize(0); }
491     override void visit(TraitsExp) { buf.setsize(0); }
492     override void visit(TupleExp) { buf.setsize(0); }
493     override void visit(TypeExp) { buf.setsize(0); }
494     override void visit(TypeidExp) { buf.setsize(0); }
495     override void visit(VoidInitExp) { buf.setsize(0); }
496 }