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