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