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 }