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 }