1 /** 2 * This file is part of DCD, a development tool for the D programming language. 3 * Copyright (C) 2014 Brian Schott 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 module dsymbol.symbol; 20 21 import stdx.allocator; 22 import stdx.allocator.mallocator : Mallocator; 23 import std.array; 24 25 import containers.ttree; 26 import containers.unrolledlist; 27 import containers.slist; 28 import containers.hashset; 29 import dparse.lexer; 30 import std.bitmanip; 31 32 import dsymbol.builtin.names; 33 public import dsymbol.string_interning; 34 35 import std.range : isOutputRange; 36 37 /** 38 * Identifies the kind of the item in an identifier completion list 39 */ 40 enum CompletionKind : char 41 { 42 /// Invalid completion kind. This is used internally and will never 43 /// be returned in a completion response. 44 dummy = '?', 45 46 /// Import symbol. This is used internally and will never 47 /// be returned in a completion response. 48 importSymbol = '*', 49 50 /// With symbol. This is used internally and will never 51 /// be returned in a completion response. 52 withSymbol = 'w', 53 54 /// class names 55 className = 'c', 56 57 /// interface names 58 interfaceName = 'i', 59 60 /// structure names 61 structName = 's', 62 63 /// union name 64 unionName = 'u', 65 66 /// variable name 67 variableName = 'v', 68 69 /// member variable 70 memberVariableName = 'm', 71 72 /// keyword, built-in version, scope statement 73 keyword = 'k', 74 75 /// function or method 76 functionName = 'f', 77 78 /// enum name 79 enumName = 'g', 80 81 /// enum member 82 enumMember = 'e', 83 84 /// package name 85 packageName = 'P', 86 87 /// module name 88 moduleName = 'M', 89 90 /// alias name 91 aliasName = 'l', 92 93 /// template name 94 templateName = 't', 95 96 /// mixin template name 97 mixinTemplateName = 'T', 98 99 /// variadic template parameter 100 variadicTmpParam = 'p', 101 102 /// type template parameter when no constraint 103 typeTmpParam = 'h', 104 } 105 106 /** 107 * Returns: true if `kind` is something that can be returned to the client 108 */ 109 bool isPublicCompletionKind(CompletionKind kind) pure nothrow @safe @nogc 110 { 111 return kind != CompletionKind.dummy && kind != CompletionKind.importSymbol 112 && kind != CompletionKind.withSymbol; 113 } 114 115 116 /** 117 * Any special information about a variable declaration symbol. 118 */ 119 enum SymbolQualifier : ubyte 120 { 121 /// None 122 none, 123 /// The symbol is an array 124 array, 125 /// The symbol is a associative array 126 assocArray, 127 /// The symbol is a function or delegate pointer 128 func, 129 /// Selective import 130 selectiveImport, 131 } 132 133 /** 134 * Autocompletion symbol 135 */ 136 struct DSymbol 137 { 138 // Copying is disabled 139 @disable this(); 140 @disable this(this); 141 142 /** 143 * Params: 144 * name = the symbol's name 145 * kind = the symbol's completion kind 146 * type = the resolved type of the symbol 147 */ 148 this(string name, CompletionKind kind = CompletionKind.dummy, DSymbol* type = null) nothrow @nogc @safe 149 { 150 this.name = istring(name); 151 this.kind = kind; 152 this.type = type; 153 } 154 /// ditto 155 this(istring name, CompletionKind kind = CompletionKind.dummy, DSymbol* type = null) nothrow @nogc @safe 156 { 157 this.name = name; 158 this.kind = kind; 159 this.type = type; 160 } 161 162 ~this() 163 { 164 foreach (ref part; parts[]) 165 { 166 if (part.owned) 167 { 168 assert(part.ptr !is null); 169 typeid(DSymbol).destroy(part.ptr); 170 } 171 else 172 part.ptr = null; 173 } 174 if (ownType) 175 typeid(DSymbol).destroy(type); 176 } 177 178 ptrdiff_t opCmp(ref const DSymbol other) const pure nothrow @nogc @safe 179 { 180 return name.opCmpFast(other.name); 181 } 182 183 bool opEquals(ref const DSymbol other) const pure nothrow @nogc @safe 184 { 185 return name == other.name; 186 } 187 188 size_t toHash() const pure nothrow @nogc @safe 189 { 190 return name.toHash(); 191 } 192 193 /** 194 * Gets all parts whose name matches the given string. 195 */ 196 inout(DSymbol)*[] getPartsByName(istring name) inout 197 { 198 auto app = appender!(DSymbol*[])(); 199 HashSet!size_t visited; 200 getParts(name, app, visited); 201 return cast(typeof(return)) app.data; 202 } 203 204 inout(DSymbol)* getFirstPartNamed(this This)(istring name) inout 205 { 206 auto app = appender!(DSymbol*[])(); 207 HashSet!size_t visited; 208 getParts(name, app, visited); 209 return app.data.length > 0 ? cast(typeof(return)) app.data[0] : null; 210 } 211 212 /** 213 * Gets all parts and imported parts. Filters based on the part's name if 214 * the `name` argument is not null. Stores results in `app`. 215 */ 216 void getParts(OR)(istring name, ref OR app, ref HashSet!size_t visited, 217 bool onlyOne = false) inout 218 if (isOutputRange!(OR, DSymbol*)) 219 { 220 import std.algorithm.iteration : filter; 221 222 if (&this is null) 223 return; 224 if (visited.contains(cast(size_t) &this)) 225 return; 226 visited.insert(cast(size_t) &this); 227 228 if (name is null) 229 { 230 foreach (part; parts[].filter!(a => a.name != IMPORT_SYMBOL_NAME)) 231 { 232 app.put(cast(DSymbol*) part); 233 if (onlyOne) 234 return; 235 } 236 DSymbol p = DSymbol(IMPORT_SYMBOL_NAME); 237 foreach (im; parts.equalRange(SymbolOwnership(&p))) 238 { 239 if (im.type !is null && !im.skipOver) 240 { 241 if (im.qualifier == SymbolQualifier.selectiveImport) 242 { 243 app.put(cast(DSymbol*) im.type); 244 if (onlyOne) 245 return; 246 } 247 else 248 im.type.getParts(name, app, visited, onlyOne); 249 } 250 } 251 } 252 else 253 { 254 DSymbol s = DSymbol(name); 255 foreach (part; parts.equalRange(SymbolOwnership(&s))) 256 { 257 app.put(cast(DSymbol*) part); 258 if (onlyOne) 259 return; 260 } 261 if (name == CONSTRUCTOR_SYMBOL_NAME || 262 name == DESTRUCTOR_SYMBOL_NAME || 263 name == UNITTEST_SYMBOL_NAME || 264 name == THIS_SYMBOL_NAME) 265 return; // these symbols should not be imported 266 267 DSymbol p = DSymbol(IMPORT_SYMBOL_NAME); 268 foreach (im; parts.equalRange(SymbolOwnership(&p))) 269 { 270 if (im.type !is null && !im.skipOver) 271 { 272 if (im.qualifier == SymbolQualifier.selectiveImport) 273 { 274 if (im.type.name == name) 275 { 276 app.put(cast(DSymbol*) im.type); 277 if (onlyOne) 278 return; 279 } 280 } 281 else 282 im.type.getParts(name, app, visited, onlyOne); 283 } 284 } 285 } 286 } 287 288 /** 289 * Returns: a range over this symbol's parts and publicly visible imports 290 */ 291 inout(DSymbol)*[] opSlice(this This)() inout 292 { 293 auto app = appender!(DSymbol*[])(); 294 HashSet!size_t visited; 295 getParts!(typeof(app))(istring(null), app, visited); 296 return cast(typeof(return)) app.data; 297 } 298 299 void addChild(DSymbol* symbol, bool owns) 300 { 301 assert(symbol !is null); 302 parts.insert(SymbolOwnership(symbol, owns)); 303 } 304 305 void addChildren(R)(R symbols, bool owns) 306 { 307 foreach (symbol; symbols) 308 { 309 assert(symbol !is null); 310 parts.insert(SymbolOwnership(symbol, owns)); 311 } 312 } 313 314 void addChildren(DSymbol*[] symbols, bool owns) 315 { 316 foreach (symbol; symbols) 317 { 318 assert(symbol !is null); 319 parts.insert(SymbolOwnership(symbol, owns)); 320 } 321 } 322 323 /** 324 * Updates the type field based on the mappings contained in the given 325 * collection. 326 */ 327 void updateTypes(ref UpdatePairCollection collection) 328 { 329 auto r = collection.equalRange(UpdatePair(type, null)); 330 if (!r.empty) 331 type = r.front.newSymbol; 332 foreach (part; parts[]) 333 part.updateTypes(collection); 334 } 335 336 /** 337 * Symbols that compose this symbol, such as enum members, class variables, 338 * methods, parameters, etc. 339 */ 340 private TTree!(SymbolOwnership, Mallocator, true, "a < b", false) parts; 341 342 /** 343 * DSymbol's name 344 */ 345 istring name; 346 347 /** 348 * Calltip to display if this is a function 349 */ 350 istring callTip; 351 352 /** 353 * Used for storing information for selective renamed imports 354 */ 355 alias altFile = callTip; 356 357 /** 358 * Module containing the symbol. 359 */ 360 istring symbolFile; 361 362 /** 363 * Documentation for the symbol. 364 */ 365 DocString doc; 366 367 /** 368 * The symbol that represents the type. 369 */ 370 // TODO: assert that the type is not a function 371 DSymbol* type; 372 373 /** 374 * Names of function arguments 375 */ 376 UnrolledList!(istring) argNames; 377 378 private uint _location; 379 380 /** 381 * DSymbol location 382 */ 383 size_t location() const pure nothrow @nogc @property @safe 384 { 385 return _location; 386 } 387 388 void location(size_t location) pure nothrow @nogc @property @safe 389 { 390 // If the symbol was declared in a file, assert that it has a location 391 // in that file. Built-in symbols don't need a location. 392 assert(symbolFile is null || location < uint.max); 393 _location = cast(uint) location; 394 } 395 396 /** 397 * The kind of symbol 398 */ 399 CompletionKind kind; 400 401 /** 402 * DSymbol qualifier 403 */ 404 SymbolQualifier qualifier; 405 406 /** 407 * If true, this symbol owns its type and will free it on destruction 408 */ 409 // dfmt off 410 mixin(bitfields!(bool, "ownType", 1, 411 bool, "skipOver", 1, 412 bool, "isPointer", 1, 413 ubyte, "", 5)); 414 // dfmt on 415 416 /// Protection level for this symbol 417 IdType protection; 418 419 } 420 421 /** 422 * istring with actual content and information if it was ditto 423 */ 424 struct DocString 425 { 426 /// Creates a non-ditto comment. 427 this(istring content) 428 { 429 this.content = content; 430 } 431 432 /// Creates a comment which may have been ditto, but has been resolved. 433 this(istring content, bool ditto) 434 { 435 this.content = content; 436 this.ditto = ditto; 437 } 438 439 alias content this; 440 441 /// Contains the documentation string associated with this symbol, resolves ditto to the previous comment with correct scope. 442 istring content; 443 /// `true` if the documentation was just a "ditto" comment copying from the previous comment. 444 bool ditto; 445 } 446 447 struct UpdatePair 448 { 449 ptrdiff_t opCmp(ref const UpdatePair other) const pure nothrow @nogc @safe 450 { 451 return (cast(ptrdiff_t) other.oldSymbol) - (cast(ptrdiff_t) this.oldSymbol); 452 } 453 454 DSymbol* oldSymbol; 455 DSymbol* newSymbol; 456 } 457 458 alias UpdatePairCollection = TTree!(UpdatePair, Mallocator, false, "a < b", false); 459 460 void generateUpdatePairs(DSymbol* oldSymbol, DSymbol* newSymbol, ref UpdatePairCollection results) 461 { 462 results.insert(UpdatePair(oldSymbol, newSymbol)); 463 foreach (part; oldSymbol.parts[]) 464 { 465 auto temp = DSymbol(oldSymbol.name); 466 auto r = newSymbol.parts.equalRange(SymbolOwnership(&temp)); 467 if (r.empty) 468 continue; 469 generateUpdatePairs(part, r.front, results); 470 } 471 } 472 473 struct SymbolOwnership 474 { 475 ptrdiff_t opCmp(ref const SymbolOwnership other) const @nogc 476 { 477 return this.ptr.opCmp(*other.ptr); 478 } 479 480 DSymbol* ptr; 481 bool owned; 482 alias ptr this; 483 }