1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 A language independent AST specific for generating mutants both via the plain
11 source code manipulation but also mutant schematas.
12 */
13 module dextool.plugin.mutate.backend.analyze.ast;
14 
15 import logger = std.experimental.logger;
16 import std.algorithm : map, filter, among;
17 import std.array : appender;
18 import std.format : formattedWrite, format;
19 import std.meta : AliasSeq;
20 import std.range : isOutputRange;
21 
22 import sumtype;
23 
24 import dextool.type : AbsolutePath, Path;
25 
26 static import dextool.plugin.mutate.backend.type;
27 
28 @safe:
29 
30 struct Ast {
31     /// The language the mutation AST is based on.
32     dextool.plugin.mutate.backend.type.Language lang;
33 
34     Location[Node] locs;
35 
36     // a node can have a type
37     TypeId[Node] nodeTypes;
38     Types types;
39 
40     // a node can have been resolved to a symbolic value.
41     SymbolId[Node] nodeSymbols;
42     Symbols symbols;
43 
44     Node root;
45 
46     void accept(VisitorT)(VisitorT v) {
47         v.visit(root);
48     }
49 
50     void put(Node n, Location l) {
51         // TODO: deduplicate the path because it will otherwise take up so much
52         // memory.....
53         locs[n] = l;
54     }
55 
56     void put(Node n, TypeId id) {
57         nodeTypes[n] = id;
58     }
59 
60     void put(Node n, SymbolId id) {
61         nodeSymbols[n] = id;
62     }
63 
64     Location location(Node n) {
65         if (auto v = n in locs) {
66             return *v;
67         }
68         return null;
69     }
70 
71     Type type(Node n) {
72         if (auto v = n in nodeTypes) {
73             return types.get(*v);
74         }
75         return null;
76     }
77 
78     Symbol symbol(Node n) {
79         if (auto v = n in nodeSymbols) {
80             return symbols.get(*v);
81         }
82         return null;
83     }
84 
85     string toString() @safe {
86         auto buf = appender!string;
87         toString(buf);
88         return buf.data;
89     }
90 
91     void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) {
92         import std.range : put;
93 
94         formattedWrite(w, "Source language: %s\n", lang);
95 
96         auto res = () @trusted {
97             auto dump = new AstPrintVisitor(&this);
98             this.accept(dump);
99             return dump.buf.data;
100         }();
101         put(w, res);
102 
103         put(w, "Types:");
104         put(w, "\n");
105         types.toString(w);
106         put(w, "Symbols:");
107         put(w, "\n");
108         symbols.toString(w);
109     }
110 }
111 
112 class AstPrintVisitor : DepthFirstVisitor {
113     import std.array : Appender;
114     import std.range : put, repeat;
115 
116     Appender!string buf;
117     int depth;
118     int prevDepth;
119     char[] indent;
120     Ast* ast;
121 
122     this(Ast* ast) {
123         this.ast = ast;
124     }
125 
126     void toBuf(Node n) {
127         import std.conv : to;
128         import colorlog : color, Color;
129 
130         if (depth == 0) {
131             indent = null;
132         } else if (depth == prevDepth) {
133         } else if (depth > prevDepth) {
134             const diff = (depth - prevDepth - 1) * 2;
135             if (indent.length == 0) {
136             } else if (indent[$ - 2] == '`') {
137                 indent[$ - 1] = ' ';
138                 indent[$ - 2] = ' ';
139             } else {
140                 indent[$ - 1] = ' ';
141             }
142 
143             foreach (_; 0 .. diff)
144                 indent ~= "  ";
145 
146             if (n.children.length <= 1)
147                 indent ~= "`-";
148             else
149                 indent ~= "|-";
150         } else {
151             const diff = (prevDepth - depth) * 2;
152             indent = indent[0 .. $ - diff];
153 
154             if (indent.length != 0 && indent[$ - 2 .. $] == "| ") {
155                 indent[$ - 1] = '-';
156             }
157         }
158         put(buf, indent);
159 
160         void printNode() {
161             auto bl = () {
162                 if (n.blacklist)
163                     return " blacklist".color(Color.magenta).toString;
164                 return "";
165             }();
166             formattedWrite(buf, "%s %x%s", n.kind.to!string.color(Color.lightGreen), () @trusted {
167                 return cast(void*) n;
168             }().to!string.color(Color.lightYellow), bl);
169         }
170 
171         void printTypeSymbol(Node n) {
172             if (auto tyId = n in ast.nodeTypes) {
173                 auto ty = ast.types.get(*tyId);
174                 formattedWrite(buf, " %X", tyId.value);
175             }
176             if (auto syId = n in ast.nodeSymbols) {
177                 auto sy = ast.symbols.get(*syId);
178                 formattedWrite(buf, " %X:%s", syId.value, sy.value);
179             }
180         }
181 
182         switch (n.kind) {
183         case Kind.Function:
184             printNode;
185             printTypeSymbol((cast(Function) n).return_);
186             break;
187         case Kind.VarDecl:
188             printNode;
189             if ((cast(VarDecl) n).isConst) {
190                 put(buf, " const");
191             }
192             break;
193         default:
194             printNode;
195             if (isExpression(n.kind)) {
196                 printTypeSymbol(n);
197             }
198         }
199 
200         if (auto l = ast.location(n)) {
201             formattedWrite(buf, " <%s:%s>", l.file.color(Color.cyan), l.posToString);
202         }
203         put(buf, "\n");
204 
205         prevDepth = depth;
206     }
207 
208     static foreach (N; Nodes) {
209         override void visit(N n) {
210             toBuf(n);
211             ++depth;
212 
213             auto op = () @trusted {
214                 if (auto v = cast(BinaryOp) n) {
215                     return v.operator;
216                 } else if (auto v = cast(UnaryOp) n) {
217                     return v.operator;
218                 }
219                 return null;
220             }();
221             if (op !is null) {
222                 visit(op);
223             }
224 
225             n.accept(this);
226             --depth;
227         }
228     }
229 }
230 
231 /// The interval in bytes that the node occupy. It is a closed->open set.
232 alias Interval = dextool.plugin.mutate.backend.type.Offset;
233 alias SourceLoc = dextool.plugin.mutate.backend.type.SourceLoc;
234 alias SourceLocRange = dextool.plugin.mutate.backend.type.SourceLocRange;
235 
236 class Location {
237     AbsolutePath file;
238     Interval interval;
239     SourceLocRange sloc;
240 
241     this(Path f, Interval i, SourceLocRange s) {
242         file = f;
243         interval = i;
244         sloc = s;
245     }
246 
247     // Convert only the position in the file to a string.
248     string posToString() @safe const {
249         return format!"[%s:%s-%s:%s]:[%s:%s]"(sloc.begin.line, sloc.begin.column,
250                 sloc.end.line, sloc.end.column, interval.begin, interval.end);
251     }
252 
253     override string toString() @safe const {
254         return format!"%s:%s"(file, posToString);
255     }
256 }
257 
258 abstract class Node {
259     Kind kind() const;
260 
261     Node[] children;
262 
263     /** If the node is blacklisted from being mutated. This is for example when
264      * the node covers a C macro.
265      */
266     bool blacklist;
267 }
268 
269 /**
270  * It is optional to add the members visitPush/visitPop to push/pop the nodes that are visited.
271  * The parent will always have been the last pushed.
272  */
273 void accept(VisitorT)(Node n, VisitorT v) {
274     static string mixinSwitch() {
275         import std.format : format;
276         import std.traits : EnumMembers;
277 
278         string s;
279         s ~= "final switch(c.kind) {\n";
280         foreach (k; [EnumMembers!Kind]) {
281             s ~= format!"case Kind.%1$s: v.visit(cast(%1$s) c); break;\n"(k);
282         }
283         s ~= "}";
284         return s;
285     }
286 
287     static if (__traits(hasMember, VisitorT, "visitPush"))
288         v.visitPush(n);
289     foreach (c; n.children) {
290         mixin(mixinSwitch);
291     }
292 
293     static if (__traits(hasMember, VisitorT, "visitPop"))
294         v.visitPop();
295 }
296 
297 /// All nodes that a visitor must be able to handle.
298 // must be sorted such that the leaf nodes are at the top.
299 // dfmt off
300 alias Nodes = AliasSeq!(
301     BinaryOp,
302     Block,
303     Branch,
304     Call,
305     Condition,
306     Expr,
307     Function,
308     Node,
309     OpAdd,
310     OpAnd,
311     OpAndBitwise,
312     OpAssign,
313     OpAssignAdd,
314     OpAssignAndBitwise,
315     OpAssignDiv,
316     OpAssignMod,
317     OpAssignMul,
318     OpAssignOrBitwise,
319     OpAssignSub,
320     OpDiv,
321     OpEqual,
322     OpGreater,
323     OpGreaterEq,
324     OpLess,
325     OpLessEq,
326     OpMod,
327     OpMul,
328     OpNegate,
329     OpNotEqual,
330     OpOr,
331     OpOrBitwise,
332     OpSub,
333     Operator,
334     Return,
335     Statement,
336     TranslationUnit,
337     UnaryOp,
338     VarDecl,
339     VarRef,
340 );
341 
342 // It should be possible to generate the enum from Nodes. How?
343 enum Kind {
344     BinaryOp,
345     Block,
346     Branch,
347     Call,
348     Condition,
349     Expr,
350     Function,
351     Node,
352     OpAdd,
353     OpAnd,
354     OpAndBitwise,
355     OpAssign,
356     OpAssignAdd,
357     OpAssignAndBitwise,
358     OpAssignDiv,
359     OpAssignMod,
360     OpAssignMul,
361     OpAssignOrBitwise,
362     OpAssignSub,
363     OpDiv,
364     OpEqual,
365     OpGreater,
366     OpGreaterEq,
367     OpLess,
368     OpLessEq,
369     OpMod,
370     OpMul,
371     OpNegate,
372     OpNotEqual,
373     OpOr,
374     OpOrBitwise,
375     OpSub,
376     Operator,
377     Return,
378     Statement,
379     TranslationUnit,
380     UnaryOp,
381     VarDecl,
382     VarRef,
383 }
384 
385 bool isExpression(Kind k) @safe pure nothrow @nogc {
386     with (Kind) {
387         return k.among(
388             BinaryOp,
389             Call,
390             Condition,
391             Expr,
392             OpAdd,
393             OpAnd,
394             OpAndBitwise,
395             OpAssign,
396             OpDiv,
397             OpEqual,
398             OpGreater,
399             OpGreaterEq,
400             OpLess,
401             OpLessEq,
402             OpMod,
403             OpMul,
404             OpNegate,
405             OpNotEqual,
406             OpOr,
407             OpOrBitwise,
408             OpSub,
409             Return,
410             UnaryOp,
411             VarDecl,
412             VarRef,
413             ) != 0;
414     }
415 }
416 // dfmt on
417 
418 interface Visitor {
419     static foreach (N; Nodes) {
420         void visit(N);
421     }
422 }
423 
424 // TODO: implement a breath first.
425 class DepthFirstVisitor : Visitor {
426     static foreach (N; Nodes) {
427         override void visit(N n) {
428             n.accept(this);
429         }
430     }
431 }
432 
433 class TranslationUnit : Node {
434     mixin NodeKind;
435 }
436 
437 class Statement : Node {
438     mixin NodeKind;
439 }
440 
441 class Expr : Node {
442     mixin NodeKind;
443 }
444 
445 /// A function definition.
446 class Function : Node {
447     mixin NodeKind;
448 
449     /// If the function has a return type it is associated with this expression.
450     Return return_;
451 }
452 
453 /// A function call.
454 class Call : Expr {
455     mixin NodeKind;
456 }
457 
458 /// The operator itself in a binary operator expression.
459 class Operator : Node {
460     mixin NodeKind;
461 }
462 
463 /** A block of code such such as a local scope enclosed by brackets, `{}`.
464  *
465  * It is intended to be possible to delete it. But it may need to be further
466  * analyzed for e.g. `Return` nodes.
467  */
468 class Block : Node {
469     mixin NodeKind;
470 }
471 
472 /** The code for one of the branches resulting from a condition.
473  *
474  * It can be the branches in a if-else statement or a case branch for languages
475  * such as C/C++.
476  *
477  * The important aspect is that the branch is not an expression. It can't be
478  * evaluated to a value of a type.
479  */
480 class Branch : Node {
481     mixin NodeKind;
482 
483     // The inside of a branch node wherein code can be injected.
484     Block inside;
485 }
486 
487 /// Results in the bottom type or up.
488 class Return : Expr {
489     mixin NodeKind;
490 }
491 
492 /// A condition wraps "something" which always evaluates to a boolean.
493 class Condition : Expr {
494     mixin NodeKind;
495 }
496 
497 class VarDecl : Expr {
498     mixin NodeKind;
499     bool isConst;
500 }
501 
502 class VarRef : Expr {
503     mixin NodeKind;
504     // should always refer to something
505     VarDecl to;
506 
507     this(VarDecl to) {
508         this.to = to;
509     }
510 
511     invariant {
512         assert(to !is null);
513     }
514 }
515 
516 class UnaryOp : Expr {
517     mixin NodeKind;
518 
519     Operator operator;
520     Expr expr;
521 
522     this() {
523     }
524 
525     this(Operator op, Expr expr) {
526         this.operator = op;
527         this.expr = expr;
528     }
529 }
530 
531 class OpNegate : UnaryOp {
532     mixin NodeKind;
533 }
534 
535 class BinaryOp : Expr {
536     mixin NodeKind;
537 
538     Operator operator;
539     Expr lhs;
540     Expr rhs;
541 
542     this() {
543     }
544 
545     this(Operator op, Expr lhs, Expr rhs) {
546         this.operator = op;
547         this.lhs = lhs;
548         this.rhs = rhs;
549     }
550 }
551 
552 class OpAssign : BinaryOp {
553     mixin NodeKind;
554 }
555 
556 class OpAssignAdd : BinaryOp {
557     mixin NodeKind;
558 }
559 
560 class OpAssignSub : BinaryOp {
561     mixin NodeKind;
562 }
563 
564 class OpAssignMul : BinaryOp {
565     mixin NodeKind;
566 }
567 
568 class OpAssignDiv : BinaryOp {
569     mixin NodeKind;
570 }
571 
572 class OpAssignMod : BinaryOp {
573     mixin NodeKind;
574 }
575 
576 class OpAssignAndBitwise : BinaryOp {
577     mixin NodeKind;
578 }
579 
580 class OpAssignOrBitwise : BinaryOp {
581     mixin NodeKind;
582 }
583 
584 class OpAdd : BinaryOp {
585     mixin NodeKind;
586 }
587 
588 class OpSub : BinaryOp {
589     mixin NodeKind;
590 }
591 
592 class OpMul : BinaryOp {
593     mixin NodeKind;
594 }
595 
596 class OpDiv : BinaryOp {
597     mixin NodeKind;
598 }
599 
600 class OpMod : BinaryOp {
601     mixin NodeKind;
602 }
603 
604 class OpAnd : BinaryOp {
605     mixin NodeKind;
606 }
607 
608 class OpAndBitwise : BinaryOp {
609     mixin NodeKind;
610 }
611 
612 class OpOr : BinaryOp {
613     mixin NodeKind;
614 }
615 
616 class OpOrBitwise : BinaryOp {
617     mixin NodeKind;
618 }
619 
620 class OpEqual : BinaryOp {
621     mixin NodeKind;
622 }
623 
624 class OpLess : BinaryOp {
625     mixin NodeKind;
626 }
627 
628 class OpGreater : BinaryOp {
629     mixin NodeKind;
630 }
631 
632 class OpLessEq : BinaryOp {
633     mixin NodeKind;
634 }
635 
636 class OpGreaterEq : BinaryOp {
637     mixin NodeKind;
638 }
639 
640 class OpNotEqual : BinaryOp {
641     mixin NodeKind;
642 }
643 
644 RetT makeId(RetT, T)(T data) {
645     import dextool.hash : makeCrc64Iso;
646 
647     auto a = makeCrc64Iso(cast(const(ubyte)[]) data);
648     return RetT(a.c0);
649 }
650 
651 RetT makeUniqueId(RetT)() {
652     import std.random : uniform;
653 
654     return RetT(uniform(long.min, long.max));
655 }
656 
657 class Type {
658     Range range;
659 
660     this() {
661         this(Range.makeInf);
662     }
663 
664     this(Range r) {
665         this.range = r;
666     }
667 
668     TypeKind kind() const {
669         return TypeKind.bottom;
670     }
671 }
672 
673 final class DiscreteType : Type {
674     this(Range r) {
675         super(r);
676     }
677 
678     override TypeKind kind() const {
679         return TypeKind.discrete;
680     }
681 }
682 
683 final class ContinuesType : Type {
684     this(Range r) {
685         super(r);
686     }
687 
688     override TypeKind kind() const {
689         return TypeKind.continues;
690     }
691 }
692 
693 final class UnorderedType : Type {
694     this(Range r) {
695         super(r);
696     }
697 
698     override TypeKind kind() const {
699         return TypeKind.unordered;
700     }
701 }
702 
703 final class BooleanType : Type {
704     this(Range r) {
705         super(r);
706     }
707 
708     override TypeKind kind() const {
709         return TypeKind.boolean;
710     }
711 }
712 
713 enum TypeKind {
714     // It can be anything, practically useless for mutation testing because it
715     // doesn't provide any logic that can be used to e.g. generate
716     // "intelligent" ROR mutants.
717     bottom,
718     /// integers, enums
719     discrete,
720     /// floating point values
721     continues,
722     /// no order exists between values in the type thus unable to do ROR
723     unordered,
724     ///
725     boolean,
726 }
727 
728 struct Value {
729     import std.traits : TemplateArgsOf;
730 
731     static struct NegInf {
732     }
733 
734     static struct PosInf {
735     }
736 
737     static struct Int {
738         // TODO: change to BigInt?
739         long value;
740     }
741 
742     static struct Bool {
743         bool value;
744     }
745 
746     alias Value = SumType!(NegInf, PosInf, Int, Bool);
747     Value value;
748 
749     static foreach (T; TemplateArgsOf!Value) {
750         this(T a) {
751             value = Value(a);
752         }
753     }
754 
755     string toString() @safe pure const {
756         auto buf = appender!string;
757         toString(buf);
758         return buf.data;
759     }
760 
761     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
762         import std.conv : to;
763         import std.range : put;
764 
765         value.match!((const NegInf a) => put(w, "-inf"), (const PosInf a) => put(w,
766                 "+inf"), (const Int a) => put(w, a.value.to!string),
767                 (const Bool a) => put(w, a.value.to!string));
768     }
769 }
770 
771 struct Range {
772     static makeInf() {
773         return Range(Value(Value.NegInf.init), Value(Value.PosInf.init));
774     }
775 
776     static makeBoolean() {
777         return Range(Value(Value.Bool(false)), Value(Value.Bool(true)));
778     }
779 
780     Value low;
781     Value up;
782 
783     this(Value low, Value up) {
784         this.low = low;
785         this.up = up;
786     }
787 
788     enum CompareResult {
789         onLowerBound,
790         onUpperBound,
791         // the value and the range fully overlap each other. This happens when
792         // the range is only one value.
793         overlap,
794         inside,
795         outside
796     }
797 
798     CompareResult compare(Value v) {
799         CompareResult negInf() {
800             return low.value.match!((Value.NegInf a) => CompareResult.onLowerBound,
801                     (Value.PosInf a) => CompareResult.outside,
802                     (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside);
803         }
804 
805         CompareResult posInf() {
806             return up.value.match!((Value.NegInf a) => CompareResult.onUpperBound,
807                     (Value.PosInf a) => CompareResult.outside,
808                     (Value.Int a) => CompareResult.outside, (Value.Bool a) => CompareResult.outside);
809         }
810 
811         CompareResult value(long v) {
812             const l = low.value.match!((Value.NegInf a) => CompareResult.inside,
813                     (Value.PosInf a) => CompareResult.outside, (Value.Int a) {
814                 if (a.value < v)
815                     return CompareResult.inside;
816                 if (a.value == v)
817                     return CompareResult.onLowerBound;
818                 return CompareResult.outside;
819             }, (Value.Bool a) => CompareResult.outside);
820 
821             const u = up.value.match!((Value.NegInf a) => CompareResult.outside,
822                     (Value.PosInf a) => CompareResult.inside, (Value.Int a) {
823                 if (a.value > v)
824                     return CompareResult.inside;
825                 if (a.value == v)
826                     return CompareResult.onUpperBound;
827                 return CompareResult.outside;
828             }, (Value.Bool a) => CompareResult.outside);
829 
830             if (l == CompareResult.inside && u == CompareResult.inside)
831                 return CompareResult.inside;
832             if (l == CompareResult.onLowerBound && u == CompareResult.onUpperBound)
833                 return CompareResult.overlap;
834             if (l == CompareResult.onLowerBound)
835                 return l;
836             if (u == CompareResult.onUpperBound)
837                 return u;
838             assert(l == CompareResult.outside || u == CompareResult.outside);
839             return CompareResult.outside;
840         }
841 
842         CompareResult boolean(bool v) {
843             // TODO: fix this
844             return CompareResult.outside;
845         }
846 
847         return v.value.match!((Value.NegInf a) => negInf,
848                 (Value.PosInf a) => posInf, (Value.Int a) => value(a.value),
849                 (Value.Bool a) => boolean(a.value));
850     }
851 
852     string toString() @safe pure const {
853         auto buf = appender!string;
854         toString(buf);
855         return buf.data;
856     }
857 
858     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
859         import std.range : put;
860 
861         put(w, "[");
862         low.toString(w);
863         put(w, ":");
864         up.toString(w);
865         put(w, "]");
866     }
867 }
868 
869 struct TypeId {
870     ulong value;
871 
872     size_t toHash() @safe pure nothrow const @nogc {
873         return value.hashOf;
874     }
875 }
876 
877 TypeId makeTypeId(T)(T data) {
878     return makeId!TypeId(data);
879 }
880 
881 TypeId makeUniqueTypeId() {
882     return makeUniqueId!TypeId;
883 }
884 
885 struct Types {
886     Type[TypeId] types;
887 
888     void require(TypeId id, Type t) {
889         if (id !in types) {
890             types[id] = t;
891         }
892     }
893 
894     void set(TypeId id, Type t) {
895         types[id] = t;
896     }
897 
898     Type get(TypeId id) {
899         if (auto v = id in types) {
900             return *v;
901         }
902         return null;
903     }
904 
905     bool hasId(TypeId id) {
906         return (id in types) !is null;
907     }
908 
909     string toString() @safe const {
910         auto buf = appender!string;
911         toString(buf);
912         return buf.data;
913     }
914 
915     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
916         import std.format : formattedWrite;
917         import std.range : put;
918 
919         foreach (kv; types.byKeyValue) {
920             formattedWrite(w, "%X:%s:%s", kv.key.value, kv.value.kind, kv.value.range);
921             put(w, "\n");
922         }
923     }
924 }
925 
926 class Symbol {
927     Value value;
928 
929     this() {
930         this(Value(Value.PosInf.init));
931     }
932 
933     this(Value v) {
934         this.value = v;
935     }
936 
937     SymbolKind kind() const {
938         return SymbolKind.unknown;
939     }
940 }
941 
942 final class DiscretSymbol : Symbol {
943     this(Value r) {
944         super(r);
945     }
946 
947     override SymbolKind kind() const {
948         return SymbolKind.discret;
949     }
950 }
951 
952 final class ContinuesSymbol : Symbol {
953     this(Value r) {
954         super(r);
955     }
956 
957     override SymbolKind kind() const {
958         return SymbolKind.continues;
959     }
960 }
961 
962 final class BooleanSymbol : Symbol {
963     this(Value r) {
964         super(r);
965     }
966 
967     override SymbolKind kind() const {
968         return SymbolKind.boolean;
969     }
970 }
971 
972 enum SymbolKind {
973     /// the symbol wasn't able to evaluate to something useful
974     unknown,
975     /// integers, enums
976     discret,
977     /// floating point values
978     continues,
979     ///
980     boolean,
981 }
982 
983 struct SymbolId {
984     ulong value;
985 
986     size_t toHash() @safe pure nothrow const @nogc {
987         return value.hashOf;
988     }
989 }
990 
991 SymbolId makeSymbolId(T)(T data) {
992     return makeId!SymbolId(data);
993 }
994 
995 SymbolId makeUniqueSymbolId() {
996     return makeUniqueId!SymbolId;
997 }
998 
999 struct Symbols {
1000     Symbol[SymbolId] symbols;
1001 
1002     void require(SymbolId id, Symbol s) {
1003         if (id !in symbols) {
1004             symbols[id] = s;
1005         }
1006     }
1007 
1008     void set(SymbolId id, Symbol s) {
1009         symbols[id] = s;
1010     }
1011 
1012     Symbol get(SymbolId id) {
1013         if (auto v = id in symbols) {
1014             return *v;
1015         }
1016         return null;
1017     }
1018 
1019     bool hasId(SymbolId id) {
1020         return (id in symbols) !is null;
1021     }
1022 
1023     string toString() @safe const {
1024         auto buf = appender!string;
1025         toString(buf);
1026         return buf.data;
1027     }
1028 
1029     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
1030         foreach (kv; symbols.byKeyValue) {
1031             formattedWrite(w, "%X:%s:%s\n", kv.key.value, kv.value.kind, kv.value.value);
1032         }
1033     }
1034 }
1035 
1036 private:
1037 
1038 mixin template NodeKind() {
1039     override Kind kind() const {
1040         import std.traits : Unqual;
1041 
1042         mixin("return Kind." ~ Unqual!(typeof(this)).stringof ~ ";");
1043     }
1044 }