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     this(DeclLocation other) @safe {
138         this = other;
139     }
140 
141     // TODO change name to anyOfDeclaratoinOrDefinition to be self explaining
142     /** A range of one location if any is set.
143      *
144      * Priority is definition -> declaration.
145      */
146     auto any() pure nothrow @nogc {
147         auto rval = only(LocationTag.init).dropOne;
148 
149         if (hasDefinition) {
150             rval = only(definition_.get);
151         } else if (hasDeclaration) {
152             rval = only(first_decl.get);
153         }
154 
155         return rval;
156     }
157 
158     @property LocationTag definition() pure nothrow @nogc {
159         return definition_.get;
160     }
161 
162     @property LocationTag definition(inout LocationTag d) {
163         // It is NOT this functions responsiblity to detect multiple
164         // definitions. Detecting involves logic, error reporting etc that is
165         // not suitable to put here.
166 
167         definition_ = d;
168         return definition_.get;
169     }
170 
171     @property LocationTag declaration() pure nothrow @nogc {
172         return first_decl.get;
173     }
174 
175     @property LocationTag declaration(inout LocationTag d) {
176         if (first_decl.isNull) {
177             first_decl = d;
178         }
179 
180         return first_decl.get;
181     }
182 
183     bool hasDefinition() pure nothrow @nogc {
184         return !definition_.isNull && definition_.get.kind != LocationTag.Kind.noloc;
185     }
186 
187     bool hasDeclaration() pure nothrow @nogc {
188         return !first_decl.isNull && first_decl.get.kind != LocationTag.Kind.noloc;
189     }
190 
191 private:
192     Nullable!LocationTag definition_;
193     Nullable!LocationTag first_decl;
194 }
195 
196 /** Contain symbols found during analyze.
197  */
198 struct Container {
199     import std.format : FormatSpec;
200 
201     private {
202         FastLookup!(TypeKind, USRType) types;
203         FastLookup!(DeclLocation, USRType) locations;
204     }
205 
206     // Forbid moving. The container is "heavy" and it results in assert errors
207     // when moved. If moving is implemented then duplication of the FastLookup
208     // need to be performed.
209     @disable this(this);
210 
211     /** Find the symbol corresponding to the key.
212      *
213      * Unified Symbol Resolution (USR).
214      *
215      * Params:
216      *   usr = key to look for.
217      */
218     auto find(T)(USRType usr) if (is(T == TypeKind))
219     out (result) {
220         logger.tracef("Find %susr:%s", result.length == 0 ? "failed, " : "", cast(string) usr);
221     }
222     do {
223         return types.find(usr);
224     }
225 
226     /** Find the location associated with the key.
227      *
228      * Unified Symbol Resolution (USR).
229      *
230      * Params:
231      *   usr = key to look for.
232      */
233     auto find(T)(USRType usr) if (is(T == LocationTag))
234     out (result) {
235         logger.tracef("Find %susr:%s", result.length == 0 ? "failed, " : "", cast(string) usr);
236     }
237     do {
238         return locations.find(usr);
239     }
240 
241     void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) {
242         import std.algorithm : map, copy;
243         import std.ascii : newline;
244         import std.format : formattedWrite, formatValue;
245         import std.range.primitives : put;
246         import cpptooling.data : splitTypeId, LocationTag;
247 
248         // avoid allocating
249 
250         put(w, "types [");
251         foreach (a; types[]) {
252             formattedWrite(w, "\n  %s %s -> %s", a.info.match!(a => typeof(a)
253                     .stringof), cast(string) a.usr, a.splitTypeId);
254         }
255         put(w, "]\n");
256         put(w, "locations [");
257         () @trusted {
258             foreach (a; locations.lookupRange) {
259                 formattedWrite(w, "\n  %s ->", cast(string) a.key);
260                 if (a.value.hasDefinition) {
261                     put(w, " ");
262                     put(w, a.value.definition.toString);
263                 }
264                 if (a.value.hasDeclaration) {
265                     put(w, "\n    ");
266                     put(w, a.value.declaration.toString);
267                 }
268             }
269         }();
270         put(w, "]");
271     }
272 
273     string toString() @safe {
274         import std.exception : assumeUnique;
275         import std.format : FormatSpec;
276 
277         char[] buf;
278         buf.reserve(100);
279         auto fmt = FormatSpec!char("%s");
280         toString((const(char)[] s) { buf ~= s; }, fmt);
281         auto trustedUnique(T)(T t) @trusted {
282             return assumeUnique(t);
283         }
284 
285         return trustedUnique(buf);
286     }
287 
288     /** Store the TypeKind.
289      *
290      * The TypeKind's usr is used as key.
291      */
292     void put(TypeKind value) @safe
293     in {
294         assert(value.usr.length > 0);
295     }
296     do {
297         if (value.usr in types.lookup) {
298             return;
299         }
300 
301         auto latest = types.put(value, value.usr);
302 
303         debug {
304             import cpptooling.data : TypeKind, toStringDecl, TypeAttr, LocationTag, Location;
305 
306             logger.tracef("Stored kind:%s usr:%s repr:%s",
307                     latest.info.match!(a => typeof(a).stringof),
308                     cast(string) latest.usr, latest.toStringDecl(TypeAttr.init, "x"));
309         }
310     }
311 
312     void put(LocationTag location, USRType usr, Flag!"isDefinition" is_definition) @safe {
313         auto found_decl = usr in locations.lookup;
314 
315         // ensure it exists
316         if (found_decl is null) {
317             DeclLocation dummy;
318             locations.put(dummy, usr);
319             found_decl = usr in locations.lookup;
320         }
321 
322         auto decl = *found_decl;
323 
324         if (is_definition) {
325             if (decl.hasDefinition) {
326                 debug logger.tracef("Definition already in container, %s (%s)",
327                         cast(string) usr, location);
328             } else {
329                 decl.definition = location;
330             }
331         } else {
332             decl.declaration = location;
333         }
334     }
335 }
336 
337 @("Should find the value corresponding to the key")
338 unittest {
339     import cpptooling.data.type : Location;
340 
341     Container cont;
342 
343     auto kind = TypeKind(Void.init, USRType("key"));
344     cont.put(kind);
345     {
346         auto result = cont.find!TypeKind(USRType("key"));
347         result.length.shouldEqual(1);
348         (cast(string) result.front.usr).shouldEqual("key");
349     }
350 
351     auto loc = LocationTag(Location("file.h", 1, 2));
352     cont.put(loc, USRType("file"), Yes.isDefinition);
353     {
354         auto result = cont.find!LocationTag(USRType("file"));
355         result.length.shouldEqual(1);
356         result.front.definition.file.shouldEqual("file.h");
357     }
358 }
359 
360 @("Should skip inserting the value if it already exist in the container")
361 unittest {
362     import cpptooling.data.type : Location;
363 
364     Container cont;
365 
366     auto kind = TypeKind(Void.init, USRType("key"));
367     cont.put(kind);
368     cont.put(kind);
369     cont.find!TypeKind(USRType("key")).length.shouldEqual(1);
370 
371     auto loc = LocationTag(Location("file.h", 1, 2));
372     cont.put(loc, USRType("file"), Yes.isDefinition), cont.put(loc,
373             USRType("file"), Yes.isDefinition),
374         cont.find!LocationTag(USRType("file")).length.shouldEqual(1);
375 }
376 
377 @("given a list of items they shall all be included in the output")
378 unittest {
379     import std.conv : to;
380     import cpptooling.data : CppClass, CppClassName;
381     import cpptooling.data.type : Location;
382 
383     Container cont;
384 
385     for (auto i = 0; i < 2; ++i) {
386         auto loc = LocationTag(Location("file" ~ to!string(i), 1, 2));
387         cont.put(loc, USRType("key" ~ to!string(i)), cast(Flag!"isDefinition")(i % 2 == 0));
388 
389         auto kind = TypeKind(Void.init, USRType("key" ~ to!string(i)));
390         cont.put(kind);
391     }
392 
393     cont.toString.shouldEqual(`types [
394   Void key0 -> TypeIdLR("", "")
395   Void key1 -> TypeIdLR("", "")]
396 locations [
397   key1 ->
398     File:file1 Line:1 Column:2
399   key0 -> File:file0 Line:1 Column:2]`);
400 }
401 
402 @("Should allow only one definition location but multiple declaration locations")
403 unittest {
404     import cpptooling.data.type : Location;
405 
406     Container cont;
407 
408     auto loc = LocationTag(Location("file.h", 1, 2));
409 }