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 }