1 /**
2 Copyright: Copyright (c) 2016, 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 module cpptooling.data.symbol.container;
11 
12 import std.typecons : Flag, Yes, No;
13 import logger = std.experimental.logger;
14 
15 import sumtype;
16 
17 //TODO move TypeKind to .data
18 import cpptooling.data.kind_type : TypeKind, Void;
19 
20 import cpptooling.data.symbol.types;
21 import cpptooling.data.type : LocationTag;
22 
23 version (unittest) {
24     import unit_threaded : Name;
25     import unit_threaded : shouldEqual;
26 }
27 
28 /** Wrapper for the results from the find-methods in Container.
29  *
30  * payload is never null.
31  */
32 @safe pure nothrow @nogc struct FindResult(T) {
33     private T* payload;
34 
35     ///
36     ref T get() @trusted
37     out (result) {
38         assert(payload !is null);
39     }
40     do {
41         return *payload;
42     }
43 
44     alias get this;
45 }
46 
47 /* The design is such that the memory location of a stored value do not
48  * change after storage.
49  * Therefor storing both the ptr and a fast lookup into the stored
50  * instances.
51  *
52  * TODO change to using the experimental allocators instead of the GC.
53  * TODO Probably ensure that K is an integer to lower the memory pressure.
54  * TODO bad name, change to something else more telling. Ok for now because the
55  * type isn't exposed.
56  */
57 private struct FastLookup(T, K) {
58     T*[] instances;
59     T*[K] lookup;
60 
61     invariant () {
62         assert(instances.length == lookup.length);
63     }
64 
65     /** The instance and corresponding key must be unique.
66      *
67      * The callers responsiblity to ensure uniqueness.  If broken, assert.
68      */
69     ref T put(T instance, in K key) @trusted
70     in {
71         assert((key in lookup) is null);
72     }
73     do {
74         auto heap = new T;
75         *heap = instance;
76 
77         instances ~= heap;
78         lookup[key] = heap;
79 
80         return *heap;
81     }
82 
83     auto find(K key) {
84         import std.range : only, dropOne;
85 
86         auto item = key in lookup;
87         if (item is null) {
88             return only(FindResult!(T)(null)).dropOne;
89         }
90 
91         return only(FindResult!(T)(*item));
92     }
93 
94     auto lookupRange() @trusted {
95         return lookup.byKeyValue();
96     }
97 
98     auto opSlice() @safe pure nothrow {
99         import std.algorithm : map;
100 
101         return instances.map!(a => *a);
102     }
103 }
104 
105 @("Should be a zero-length range")
106 unittest {
107     auto inst = FastLookup!(int, int)();
108     auto result = inst.find(0);
109 
110     result.length.shouldEqual(0);
111 }
112 
113 /** Location of all definitions (fallback, declaration).
114  *
115  * Assuming that there can only be one definition but multiple declarations.
116  *
117  * A location of kind "noloc" do NOT mean that there exist a
118  * definition/declaration. See the hasXXX-methods.
119  *
120  * Only the first declaration is saved to be used as a fallback for those
121  * occasions when a definition isn't found.
122  *
123  * Remember that for those users of DeclLocation that do not have a
124  * cache/delaying the decision until all files have been analyzed may base
125  * their decision on the first occurens. Therefor to be less surprising to the
126  * user the first one is saved and the rest discarded.
127  *
128  * TODO save all declarations? Remember that it may take a lot of memory.
129  *
130  * Hint, the name DeclLocation was chosen because a definition is a declaration
131  * so it encapsulates both.
132  */
133 private @safe struct DeclLocation {
134     import std.range : only, dropOne;
135     import std.typecons : Nullable;
136 
137     // TODO change name to anyOfDeclaratoinOrDefinition to be self explaining
138     /** A range of one location if any is set.
139      *
140      * Priority is definition -> declaration.
141      */
142     auto any() pure nothrow @nogc {
143         auto rval = only(LocationTag.init).dropOne;
144 
145         if (hasDefinition) {
146             rval = only(definition_.get);
147         } else if (hasDeclaration) {
148             rval = only(first_decl.get);
149         }
150 
151         return rval;
152     }
153 
154     @property LocationTag definition() pure nothrow @nogc {
155         return definition_.get;
156     }
157 
158     @property LocationTag definition(inout LocationTag d) {
159         // It is NOT this functions responsiblity to detect multiple
160         // definitions. Detecting involves logic, error reporting etc that is
161         // not suitable to put here.
162 
163         definition_ = d;
164         return definition_.get;
165     }
166 
167     @property LocationTag declaration() pure nothrow @nogc {
168         return first_decl.get;
169     }
170 
171     @property LocationTag declaration(inout LocationTag d) {
172         if (first_decl.isNull) {
173             first_decl = d;
174         }
175 
176         return first_decl.get;
177     }
178 
179     bool hasDefinition() pure nothrow @nogc {
180         return !definition_.isNull && definition_.get.kind != LocationTag.Kind.noloc;
181     }
182 
183     bool hasDeclaration() pure nothrow @nogc {
184         return !first_decl.isNull && first_decl.get.kind != LocationTag.Kind.noloc;
185     }
186 
187 private:
188     Nullable!LocationTag definition_;
189     Nullable!LocationTag first_decl;
190 }
191 
192 /** Contain symbols found during analyze.
193  */
194 struct Container {
195     import std.format : FormatSpec;
196 
197     private {
198         FastLookup!(TypeKind, USRType) types;
199         FastLookup!(DeclLocation, USRType) locations;
200     }
201 
202     // Forbid moving. The container is "heavy" and it results in assert errors
203     // when moved. If moving is implemented then duplication of the FastLookup
204     // need to be performed.
205     @disable this(this);
206 
207     /** Find the symbol corresponding to the key.
208      *
209      * Unified Symbol Resolution (USR).
210      *
211      * Params:
212      *   usr = key to look for.
213      */
214     auto find(T)(USRType usr) if (is(T == TypeKind))
215     out (result) {
216         logger.tracef("Find %susr:%s", result.length == 0 ? "failed, " : "", cast(string) usr);
217     }
218     do {
219         return types.find(usr);
220     }
221 
222     /** Find the location associated with the key.
223      *
224      * Unified Symbol Resolution (USR).
225      *
226      * Params:
227      *   usr = key to look for.
228      */
229     auto find(T)(USRType usr) if (is(T == LocationTag))
230     out (result) {
231         logger.tracef("Find %susr:%s", result.length == 0 ? "failed, " : "", cast(string) usr);
232     }
233     do {
234         return locations.find(usr);
235     }
236 
237     void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) {
238         import std.algorithm : map, copy;
239         import std.ascii : newline;
240         import std.format : formattedWrite, formatValue;
241         import std.range.primitives : put;
242         import cpptooling.data : splitTypeId, LocationTag;
243 
244         // avoid allocating
245 
246         put(w, "types [");
247         foreach (a; types[]) {
248             formattedWrite(w, "\n  %s %s -> %s", a.info.match!(a => typeof(a)
249                     .stringof), cast(string) a.usr, a.splitTypeId);
250         }
251         put(w, "]\n");
252         put(w, "locations [");
253         () @trusted {
254             foreach (a; locations.lookupRange) {
255                 formattedWrite(w, "\n  %s ->", cast(string) a.key);
256                 if (a.value.hasDefinition) {
257                     put(w, " ");
258                     put(w, a.value.definition.toString);
259                 }
260                 if (a.value.hasDeclaration) {
261                     put(w, "\n    ");
262                     put(w, a.value.declaration.toString);
263                 }
264             }
265         }();
266         put(w, "]");
267     }
268 
269     string toString() @safe {
270         import std.exception : assumeUnique;
271         import std.format : FormatSpec;
272 
273         char[] buf;
274         buf.reserve(100);
275         auto fmt = FormatSpec!char("%s");
276         toString((const(char)[] s) { buf ~= s; }, fmt);
277         auto trustedUnique(T)(T t) @trusted {
278             return assumeUnique(t);
279         }
280 
281         return trustedUnique(buf);
282     }
283 
284     /** Store the TypeKind.
285      *
286      * The TypeKind's usr is used as key.
287      */
288     void put(TypeKind value) @safe
289     in {
290         assert(value.usr.length > 0);
291     }
292     do {
293         if (value.usr in types.lookup) {
294             return;
295         }
296 
297         auto latest = types.put(value, value.usr);
298 
299         debug {
300             import cpptooling.data : TypeKind, toStringDecl, TypeAttr, LocationTag, Location;
301 
302             logger.tracef("Stored kind:%s usr:%s repr:%s",
303                     latest.info.match!(a => typeof(a).stringof),
304                     cast(string) latest.usr, latest.toStringDecl(TypeAttr.init, "x"));
305         }
306     }
307 
308     void put(LocationTag location, USRType usr, Flag!"isDefinition" is_definition) @safe {
309         auto found_decl = usr in locations.lookup;
310 
311         // ensure it exists
312         if (found_decl is null) {
313             DeclLocation dummy;
314             locations.put(dummy, usr);
315             found_decl = usr in locations.lookup;
316         }
317 
318         auto decl = *found_decl;
319 
320         if (is_definition) {
321             if (decl.hasDefinition) {
322                 debug logger.tracef("Definition already in container, %s (%s)",
323                         cast(string) usr, location);
324             } else {
325                 decl.definition = location;
326             }
327         } else {
328             decl.declaration = location;
329         }
330     }
331 }
332 
333 @("Should find the value corresponding to the key")
334 unittest {
335     import cpptooling.data.type : Location;
336 
337     Container cont;
338 
339     auto kind = TypeKind(Void.init, USRType("key"));
340     cont.put(kind);
341     {
342         auto result = cont.find!TypeKind(USRType("key"));
343         result.length.shouldEqual(1);
344         (cast(string) result.front.usr).shouldEqual("key");
345     }
346 
347     auto loc = LocationTag(Location("file.h", 1, 2));
348     cont.put(loc, USRType("file"), Yes.isDefinition);
349     {
350         auto result = cont.find!LocationTag(USRType("file"));
351         result.length.shouldEqual(1);
352         result.front.definition.file.shouldEqual("file.h");
353     }
354 }
355 
356 @("Should skip inserting the value if it already exist in the container")
357 unittest {
358     import cpptooling.data.type : Location;
359 
360     Container cont;
361 
362     auto kind = TypeKind(Void.init, USRType("key"));
363     cont.put(kind);
364     cont.put(kind);
365     cont.find!TypeKind(USRType("key")).length.shouldEqual(1);
366 
367     auto loc = LocationTag(Location("file.h", 1, 2));
368     cont.put(loc, USRType("file"), Yes.isDefinition), cont.put(loc,
369             USRType("file"), Yes.isDefinition),
370         cont.find!LocationTag(USRType("file")).length.shouldEqual(1);
371 }
372 
373 @("given a list of items they shall all be included in the output")
374 unittest {
375     import std.conv : to;
376     import cpptooling.data : CppClass, CppClassName;
377     import cpptooling.data.type : Location;
378 
379     Container cont;
380 
381     for (auto i = 0; i < 2; ++i) {
382         auto loc = LocationTag(Location("file" ~ to!string(i), 1, 2));
383         cont.put(loc, USRType("key" ~ to!string(i)), cast(Flag!"isDefinition")(i % 2 == 0));
384 
385         auto kind = TypeKind(Void.init, USRType("key" ~ to!string(i)));
386         cont.put(kind);
387     }
388 
389     cont.toString.shouldEqual(`types [
390   Void key0 -> TypeIdLR("", "")
391   Void key1 -> TypeIdLR("", "")]
392 locations [
393   key1 ->
394     File:file1 Line:1 Column:2
395   key0 -> File:file0 Line:1 Column:2]`);
396 }
397 
398 @("Should allow only one definition location but multiple declaration locations")
399 unittest {
400     import cpptooling.data.type : Location;
401 
402     Container cont;
403 
404     auto loc = LocationTag(Location("file.h", 1, 2));
405 }