1 module d.ir.dscope;
2 
3 import d.ir.symbol;
4 
5 import d.ast.conditional;
6 
7 import source.location;
8 import source.name;
9 
10 struct ConditionalBranch {
11 // XXX: Can't be private because mixin templates are a glorious hack.
12 // private:
13 	// XXX: Because mixin if such a fucking broken way to go:
14 	static import d.ast.declaration;
15 	alias Declaration = d.ast.declaration.Declaration;
16 	
17 	import std.bitmanip;
18 	mixin(taggedClassRef!(
19 		StaticIfDeclaration, "sif",
20 		bool, "branch", 1,
21 	));
22 	
23 public:
24 	this(StaticIfDeclaration sif, bool branch) {
25 		// XXX: Need to set the branch first because of
26 		// https://issues.dlang.org/show_bug.cgi?id=15305
27 		this.branch = branch;
28 		this.sif = sif;
29 	}
30 }
31 
32 /**
33  * Tools to make symbols Scopes.
34  */
35 interface Scope {
36 	Module getModule();
37 	Scope getParentScope();
38 	
39 	Module[] getImports();
40 	void addImport(Module m);
41 	
42 	Symbol search(Location location, Name name);
43 	Symbol resolve(Location location, Name name);
44 	
45 	void addSymbol(Symbol s);
46 	void addOverloadableSymbol(Symbol s);
47 	void addConditionalSymbol(Symbol s, ConditionalBranch[] cdBranches);
48 	
49 	void setPoisoningMode();
50 	void clearPoisoningMode();
51 }
52 
53 enum ScopeType {
54 	Module,
55 	WithParent,
56 	Nested,
57 }
58 
59 mixin template ScopeImpl(
60 	ScopeType ST = ScopeType.WithParent,
61 	ParentScope = Scope,
62 ) {
63 private:
64 	import d.ir.symbol;
65 	Module dmodule;
66 	static if (ST) {
67 		ParentScope parentScope;
68 	}
69 	
70 	import source.name;
71 	Symbol[Name] symbols;
72 	
73 	static if (ST) {
74 		// XXX: Use a proper set :D
75 		bool[Variable] captures;
76 	}
77 	
78 	Module[] imports;
79 	
80 	bool isPoisoning;
81 	bool isPoisoned;
82 	bool hasConditional;
83 	
84 final:
85 	static if (ST) {
86 		void fillParentScope(ParentScope parentScope) {
87 			this.dmodule = parentScope.getModule();
88 			this.parentScope = parentScope;
89 		}
90 	}
91 	
92 public:
93 	Module getModule() {
94 		assert(dmodule !is null, "No module");
95 		return dmodule;
96 	}
97 	
98 	ParentScope getParentScope() {
99 		static if (ST) {
100 			assert(parentScope !is null);
101 			return parentScope;
102 		} else {
103 			return null;
104 		}
105 	}
106 	
107 	static if (ST) {
108 		bool[Variable] getCaptures() {
109 			return captures;
110 		}
111 	}
112 	
113 	Module[] getImports() {
114 		return imports;
115 	}
116 	
117 	void addImport(Module m) {
118 		imports ~= m;
119 	}
120 	
121 	Symbol search(Location location, Name name) {
122 		if (auto s = resolve(location, name)) {
123 			return s;
124 		}
125 		
126 		Symbol s = null;
127 		static if (ST) {
128 			s = parentScope.search(location, name);
129 		}
130 		
131 		static if (!is(typeof(hasContext))) {
132 			enum hasContext = false;
133 		}
134 		
135 		if (s is null || !hasContext) {
136 			return s;
137 		}
138 		
139 		static if (ST) {
140 			if (auto v = cast(Variable) s) {
141 				import d.common.qualifier;
142 				if (v.storage.isLocal) {
143 					captures[v] = true;
144 					v.storage = Storage.Capture;
145 				}
146 			}
147 		}
148 		
149 		return s;
150 	}
151 	
152 	Symbol resolve(Location location, Name name) {
153 		auto sPtr = name in symbols;
154 		if (sPtr is null) {
155 			if (isPoisoning) {
156 				symbols[name] = new Poison(location, name);
157 				isPoisoned = true;
158 			}
159 			
160 			return null;
161 		}
162 		
163 		auto s = *sPtr;
164 		
165 		static if (ST == ScopeType.Nested) {
166 			// For nested scope we unpack and/or mark overloadset as already
167 			// resolved so we make copy of it when adding new overloads.
168 			if (auto os = cast(OverloadSet) s) {
169 				os.isPoisoned = isPoisoning;
170 				if (os.set.length == 1) {
171 					return os.set[0];
172 				}
173 				
174 				os.isResolved = true;
175 				return os;
176 			}
177 		}
178 		
179 		// If we are poisoning, we need to make sure we poison.
180 		// If not, we can return directly.
181 		if (!isPoisoning) {
182 			return s;
183 		}
184 		
185 		static if (ST != ScopeType.Nested) {
186 			// If we have an overloadset, make it poisoned.
187 			if (auto os = cast(OverloadSet) s) {
188 				os.isPoisoned = true;
189 				return s;
190 			}
191 		}
192 		
193 		// If we have a poison, then pretend there is nothing.
194 		if (cast(Poison) s) {
195 			return null;
196 		}
197 		
198 		// If we have no conditionals, no need to check for them.
199 		if (!hasConditional) {
200 			return s;
201 		}
202 		
203 		// If we have a conditional, poison it.
204 		if (auto cs = cast(ConditionalSet) s) {
205 			cs.isPoisoned = true;
206 			return cs.selected;
207 		}
208 		
209 		return s;
210 	}
211 	
212 	void addSymbol(Symbol s) {
213 		assert(
214 			!s.name.isEmpty,
215 			"Symbol can't be added to scope as it has no name."
216 		);
217 		
218 		if (auto sPtr = s.name in symbols) {
219 			if (auto p = cast(Poison) *sPtr) {
220 				import source.exception;
221 				throw new CompileException(s.location, "Poisoned");
222 			}
223 			
224 			import source.exception;
225 			throw new CompileException(s.location, "Already defined");
226 		}
227 		
228 		symbols[s.name] = s;
229 	}
230 	
231 	void addOverloadableSymbol(Symbol s) {
232 		if (auto sPtr = s.name in symbols) {
233 			if (auto os = cast(OverloadSet) *sPtr) {
234 				if (os.isPoisoned) {
235 					import source.exception;
236 					throw new CompileException(s.location, "Poisoned");
237 				}
238 				
239 				if (ST == ScopeType.Nested && os.isResolved) {
240 					*sPtr = os = os.clone();
241 				}
242 				
243 				os.set ~= s;
244 				return;
245 			}
246 		}
247 		
248 		addSymbol(new OverloadSet(s.location, s.name, [s]));
249 	}
250 	
251 	void addConditionalSymbol(Symbol s, ConditionalBranch[] cdBranches) in {
252 		assert(cdBranches.length > 0, "No conditional branches supplied");
253 	} do {
254 		auto entry = ConditionalEntry(s, cdBranches);
255 		if (auto csPtr = s.name in symbols) {
256 			if (auto cs = cast(ConditionalSet) *csPtr) {
257 				cs.set ~= entry;
258 				return;
259 			}
260 			
261 			import source.exception;
262 			throw new CompileException(s.location, "Already defined");
263 		}
264 		
265 		symbols[s.name] = new ConditionalSet(s.location, s.name, [entry]);
266 		hasConditional = true;
267 	}
268 	
269 	void setPoisoningMode() in {
270 		assert(isPoisoning == false, "poisoning mode is already on.");
271 	} do {
272 		isPoisoning = true;
273 	}
274 	
275 	void clearPoisoningMode() in {
276 		assert(isPoisoning == true, "poisoning mode is not on.");
277 	} do {
278 		// XXX: Consider not removing tags on OverloadSet.
279 		// That would allow to not pass over the AA most of the time.
280 		foreach(n; symbols.keys) {
281 			auto s = symbols[n];
282 			
283 			// Mark overload set as non poisoned.
284 			// XXX: Why ?!??
285 			if (auto os = cast(OverloadSet) s) {
286 				os.isPoisoned = false;
287 				continue;
288 			}
289 			
290 			// Remove poisons.
291 			// XXX: Why ?!!??!
292 			if (isPoisoned && cast(Poison) s) {
293 				symbols.remove(n);
294 				continue;
295 			}
296 			
297 			// If we have no conditionals, no need to check for them.
298 			if (!hasConditional) {
299 				continue;
300 			}
301 			
302 			// Replace conditional entrie by whatever they resolve to.
303 			if (auto cs = cast(ConditionalSet) s) {
304 				if (cs.set.length) {
305 					import source.exception;
306 					throw new CompileException(
307 						cs.set[0].entry.location,
308 						"Not resolved",
309 					);
310 				}
311 				
312 				assert(
313 					cs.set.length == 0,
314 					"Conditional symbols remains when clearing poisoning mode."
315 				);
316 				if (cs.selected) {
317 					symbols[n] = cs.selected;
318 				} else {
319 					symbols.remove(n);
320 				}
321 			}
322 		}
323 		
324 		isPoisoning = false;
325 		isPoisoned = false;
326 		hasConditional = false;
327 	}
328 	
329 	// XXX: Use of smarter data structure can probably improve things here :D
330 	import d.ast.conditional : StaticIfDeclaration;
331 	void resolveConditional(StaticIfDeclaration sif, bool branch) in {
332 		assert(
333 			isPoisoning,
334 			"You must be in poisoning mode when resolving static ifs."
335 		);
336 	} do {
337 		foreach(s; symbols.values) {
338 			if (auto cs = cast(ConditionalSet) s) {
339 				ConditionalEntry[] newSet;
340 				foreach(ce; cs.set) {
341 					// This is not the symbol we are interested in, move on.
342 					if (ce.cdBranches[0].sif !is sif) {
343 						newSet ~= ce;
344 						continue;
345 					}
346 					
347 					// If this is not the right branch, forget.
348 					if (ce.cdBranches[0].branch != branch) {
349 						continue;
350 					}
351 					
352 					// The top level static if is resolved, drop.
353 					ce.cdBranches = ce.cdBranches[1 .. $];
354 					
355 					// There are nested static ifs, put back in the set.
356 					if (ce.cdBranches.length) {
357 						newSet ~= ce;
358 						continue;
359 					}
360 					
361 					// FIXME: Check if it is an overloadable symbol.
362 					assert(cs.selected is null, "overload ? bug ?");
363 					
364 					// We have a new symbol, select it.
365 					if (cs.isPoisoned) {
366 						import source.exception;
367 						throw new CompileException(s.location, "Poisoned");
368 					}
369 					
370 					cs.selected = ce.entry;
371 				}
372 				
373 				cs.set = newSet;
374 			}
375 		}
376 	}
377 }
378 
379 final:
380 
381 /**
382  * A scope associate identifier with declarations.
383  */
384 class NestedScope : Scope {
385 	// TODO: Remove the module field, which can be access from fun.
386 	Function fun;
387 	
388 	mixin ScopeImpl!(ScopeType.Nested);
389 	
390 	this(Function fun) {
391 		this.fun = fun;
392 		this.parentScope = fun;
393 	}
394 	
395 	this(NestedScope parentScope) {
396 		this.fun = parentScope.fun;
397 		this.parentScope = parentScope;
398 	}
399 	
400 	this(Scope s) {
401 		if (auto n = cast(NestedScope) s) {
402 			this(n);
403 		} else if (auto f = cast(Function) s) {
404 			this(f);
405 		} else {
406 			assert(0, "Parent scope must be a function or a nested scope");
407 		}
408 	}
409 	
410 	Module getModule() {
411 		return fun.getModule();
412 	}
413 }
414 
415 // XXX: Can't be private because mixin templates are a glorious hack.
416 // private:
417 class Poison : Symbol {
418 	this(Location location, Name name) {
419 		super(location, name);
420 	}
421 }
422 
423 struct ConditionalEntry {
424 	Symbol entry;
425 	ConditionalBranch[] cdBranches;
426 }
427 
428 class ConditionalSet : Symbol {
429 	ConditionalEntry[] set;
430 	
431 	Symbol selected;
432 	
433 	this(Location location, Name name, ConditionalEntry[] set) {
434 		super(location, name);
435 		this.set = set;
436 	}
437 }