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 }