Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ShaderCompilationContext.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 using SiliconStudio.Paradox.Shaders.Parser.Analysis;
8 using SiliconStudio.Paradox.Shaders.Parser.Ast;
9 using SiliconStudio.Paradox.Shaders.Parser.Utility;
10 using SiliconStudio.Shaders.Ast;
11 using SiliconStudio.Shaders.Ast.Hlsl;
12 using SiliconStudio.Shaders.Parser;
13 using SiliconStudio.Shaders.Utility;
14 
15 namespace SiliconStudio.Paradox.Shaders.Parser.Mixins
16 {
17  internal class ShaderCompilationContext
18  {
19  #region Private static members
20 
21  /// <summary>
22  /// A lock to perform a thread safe analysis of the mixins.
23  /// </summary>
24  private static readonly Object AnalysisLock = new Object();
25 
26  #endregion
27 
28  #region Public members
29 
30  /// <summary>
31  /// List of all the mixins
32  /// </summary>
33  public HashSet<ModuleMixinInfo> MixinInfos = new HashSet<ModuleMixinInfo>();
34 
35  /// <summary>
36  /// Log of all the warnings and errors
37  /// </summary>
38  private LoggerResult ErrorWarningLog;
39 
40  #endregion
41 
42  #region Constructor
43 
44  /// <summary>
45  /// Default constructor for cloning
46  /// </summary>
47  public ShaderCompilationContext(LoggerResult log)
48  {
49  if (log == null) throw new ArgumentNullException("log");
50 
51  ErrorWarningLog = log;
52  }
53 
54  #endregion
55 
56  #region Public methods
57 
58  public void Run(){}
59 
60  /// <summary>
61  /// Runs the first step of the analysis on the context
62  /// </summary>
63  /// <param name="mixinInfos">the context</param>
64  public void Preprocess(HashSet<ModuleMixinInfo> mixinInfos)
65  {
66  MixinInfos = mixinInfos;
67 
68  BuildModuleMixins(MixinInfos);
69  }
70 
71  /// <summary>
72  /// Specifically analyze a module
73  /// </summary>
74  /// <param name="mixinInfo">the ModuleMixinInfo</param>
75  public void Analyze(ModuleMixinInfo mixinInfo)
76  {
77  ModuleSemanticAnalysisPerMixin(mixinInfo);
78  }
79 
80  /// <summary>
81  /// Get the module mixin based on the ShaderSource
82  /// </summary>
83  /// <param name="shaderSource">the ShaderSource</param>
84  /// <returns>the ModuleMixin</returns>
85  public ModuleMixin GetModuleMixinFromShaderSource(ShaderSource shaderSource)
86  {
87  var found = MixinInfos.FirstOrDefault(x => x.ShaderSource.Equals(shaderSource));
88  return found == null ? null : found.Mixin;
89  }
90 
91  #endregion
92 
93  #region Private methods
94 
95  /// <summary>
96  /// Get all the declarations of all the mixins
97  /// </summary>
98  /// <param name="mixinInfos">list of ModuleMixinInfo</param>
99  private void BuildModuleMixins(HashSet<ModuleMixinInfo> mixinInfos)
100  {
101  // type analysis
102  foreach (var mixinInfo in mixinInfos)
103  PerformTypeAnalysis(mixinInfo);
104  foreach (var mixinInfo in mixinInfos)
105  BuildModuleMixin(mixinInfo);
106 
107  lock (AnalysisLock)
108  {
109  foreach (var mixinInfo in mixinInfos)
110  BuildMixinDependencies(mixinInfo);
111 
112  // build Virtual tables
113  foreach (var mixinInfo in mixinInfos)
114  BuildVirtualTables(mixinInfo);
115  }
116  }
117 
118  /// <summary>
119  /// Get all the declarations in the mixin
120  /// </summary>
121  /// <param name="mixinInfo">The mixin info</param>
122  private void BuildModuleMixin(ModuleMixinInfo mixinInfo)
123  {
124  lock (mixinInfo)
125  {
126  if (mixinInfo.Mixin.ModuleMixinBuildStatus == AnalysisStatus.Complete)
127  return;
128 
129  if (mixinInfo.Mixin.ModuleMixinBuildStatus != AnalysisStatus.None)
130  throw new Exception("BuildModuleMixin failed for mixin " + mixinInfo.MixinName);
131 
132  mixinInfo.Mixin.ModuleMixinBuildStatus = AnalysisStatus.InProgress;
133 
134  var mixinAst = mixinInfo.MixinAst;
135 
136  var moduleMixin = mixinInfo.Mixin;
137  moduleMixin.SetShaderAst(mixinAst);
138  moduleMixin.MixinGenericName = mixinInfo.MixinGenericName;
139 
140  if (mixinAst != null && mixinInfo.Instanciated)
141  {
142  var vtindex = 0;
143  foreach (var member in mixinAst.Members)
144  {
145  if (member is MethodDeclaration)
146  {
147  moduleMixin.LocalVirtualTable.Methods.Add(new MethodDeclarationShaderCouple(member as MethodDeclaration, mixinAst));
148  member.SetTag(ParadoxTags.VirtualTableReference, new VTableReference { Shader = mixinInfo.MixinName, Slot = vtindex++ });
149  }
150  else if (member is Variable)
151  {
152  var variable = member as Variable;
153  moduleMixin.LocalVirtualTable.Variables.Add(new VariableShaderCouple(variable, mixinAst));
154  // remove null initial values
155  var initValue = variable.InitialValue as VariableReferenceExpression;
156  if (initValue != null && initValue.Name.Text == "null")
157  variable.InitialValue = null;
158 
159  }
160  else if (member is Typedef)
161  moduleMixin.LocalVirtualTable.Typedefs.Add(member as Typedef);
162  else if (member is StructType)
163  moduleMixin.LocalVirtualTable.StructureTypes.Add(member as StructType);
164  else
165  moduleMixin.RemainingNodes.Add(member);
166 
167  // set a tag on the members to easily recognize them from the local declarations/definitions
168  member.SetTag(ParadoxTags.ShaderScope, moduleMixin);
169  member.SetTag(ParadoxTags.BaseDeclarationMixin, mixinInfo.MixinName);
170  }
171 
172  // Check name conflicts
173  foreach (var method in moduleMixin.LocalVirtualTable.Methods)
174  {
175  if (moduleMixin.LocalVirtualTable.Methods.Count(x => x.Method.IsSameSignature(method.Method)) > 1) // 1 because the function is in the list
176  ErrorWarningLog.Error(ParadoxMessageCode.ErrorFunctionRedefined, method.Method.Span, method.Method, mixinInfo.MixinName);
177 
178  if (moduleMixin.LocalVirtualTable.Variables.Any(x => x.Variable.Name.Text == method.Method.Name.Text))
179  ErrorWarningLog.Error(ParadoxMessageCode.ErrorFunctionVariableNameConflict, method.Method.Span, method.Method, mixinInfo.MixinName);
180  }
181  foreach (var variable in moduleMixin.LocalVirtualTable.Variables)
182  {
183  if (moduleMixin.LocalVirtualTable.Variables.Count(x => x.Variable.Name.Text == variable.Variable.Name.Text) > 1) // 1 because the function is in the list
184  ErrorWarningLog.Error(ParadoxMessageCode.ErrorFunctionVariableNameConflict, variable.Variable.Span, variable.Variable, mixinInfo.MixinName);
185 
186  if (moduleMixin.LocalVirtualTable.Methods.Any(x => x.Method.Name.Text == variable.Variable.Name.Text))
187  ErrorWarningLog.Error(ParadoxMessageCode.ErrorVariableNameConflict, variable.Variable.Span, variable.Variable, mixinInfo.MixinName);
188  }
189  }
190 
191  moduleMixin.MinimalContext = new HashSet<ModuleMixin>(mixinInfo.MinimalContext.Select(x => x.Mixin));
192 
193  mixinInfo.Mixin.ModuleMixinBuildStatus = AnalysisStatus.Complete;
194  }
195  }
196 
197  /// <summary>
198  /// Get the list of dependencies for the mixin (base classes only)
199  /// </summary>
200  /// <param name="mixinInfo">the mixin info</param>
201  /// <returns>A collection of class names</returns>
202  private void BuildMixinDependencies(ModuleMixinInfo mixinInfo)
203  {
204  var mixin = mixinInfo.Mixin;
205 
206  if (mixin.DependenciesStatus == AnalysisStatus.Cyclic || mixin.DependenciesStatus == AnalysisStatus.Error || mixinInfo.Mixin.DependenciesStatus == AnalysisStatus.Complete)
207  return;
208  if (mixin.DependenciesStatus == AnalysisStatus.InProgress)
209  {
210  ErrorWarningLog.Error(ParadoxMessageCode.ErrorCyclicDependency, mixin.Shader.Span, mixin.Shader);
211  mixin.DependenciesStatus = AnalysisStatus.Cyclic;
212  return;
213  }
214  if (mixin.DependenciesStatus == AnalysisStatus.None)
215  {
216  mixin.DependenciesStatus = AnalysisStatus.InProgress;
217 
218  foreach (var baseClass in mixin.Shader.BaseClasses)
219  {
220  // search based on class name and macros. It is enough since a ShaderMixinSource only have ShaderClassSource as base mixin (and no ShaderMixinSource that may redefine the macros)
221  var bcInfo = MixinInfos.FirstOrDefault(x => x.MixinName == baseClass.Name.Text && AreMacrosEqual(x.Macros, mixinInfo.Macros));
222 
223  if (bcInfo == null)
224  {
225  ErrorWarningLog.Error(ParadoxMessageCode.ErrorDependencyNotInModule, baseClass.Span, baseClass, mixin.MixinName);
226  mixin.DependenciesStatus = AnalysisStatus.Error;
227  return;
228  }
229 
230  var bc = bcInfo.Mixin;
231 
232  BuildMixinDependencies(bcInfo);
233  if (bc.DependenciesStatus == AnalysisStatus.Error || bc.DependenciesStatus == AnalysisStatus.Cyclic)
234  {
235  mixin.DependenciesStatus = AnalysisStatus.Error;
236  return;
237  }
238 
239  foreach (var dependency in bc.InheritanceList)
240  {
241  if (!mixin.InheritanceList.Contains(dependency))
242  mixin.InheritanceList.Add(dependency);
243  }
244 
245  if (!mixin.InheritanceList.Contains(bc))
246  mixin.InheritanceList.Add(bc);
247 
248  mixin.BaseMixins.Add(bc);
249 
250  if (!bcInfo.Instanciated)
251  mixinInfo.Instanciated = false;
252  }
253 
254  // do not look for extern keyword but for type name
255  foreach (var variable in mixin.LocalVirtualTable.Variables)
256  {
257  var variableTypeName = variable.Variable.Type.Name.Text;
258  if (variable.Variable.Type is ArrayType) // support for array of externs
259  variableTypeName = (variable.Variable.Type as ArrayType).Type.Name.Text;
260 
261  var baseClassInfo = MixinInfos.FirstOrDefault(x => x.MixinName == variableTypeName);
262  if (baseClassInfo != null)
263  {
264  variable.Variable.Qualifiers |= SiliconStudio.Shaders.Ast.Hlsl.StorageQualifier.Extern; // add the extern keyword but simpler analysis in the future
265  if (variable.Variable.InitialValue is VariableReferenceExpression && (variable.Variable.InitialValue as VariableReferenceExpression).Name.Text == "stage")
266  mixin.StageInitVariableDependencies.Add(variable.Variable, baseClassInfo.Mixin);
267  else
268  mixin.VariableDependencies.Add(variable.Variable, baseClassInfo.Mixin);
269 
270  if (variable.Variable.Type is ArrayType)
271  {
272  var typeArray = variable.Variable.Type as ArrayType;
273  typeArray.Type.TypeInference.Declaration = baseClassInfo.MixinAst;
274  }
275  else
276  {
277  variable.Variable.Type.TypeInference.Declaration = baseClassInfo.MixinAst;
278  }
279  }
280  }
281 
282  mixin.DependenciesStatus = AnalysisStatus.Complete;
283  }
284  }
285 
286  /// <summary>
287  /// Performs type analysis for each mixin
288  /// </summary>
289  /// <param name="mixinInfo">the ModuleMixinInfo</param>
290  private void PerformTypeAnalysis(ModuleMixinInfo mixinInfo)
291  {
292  lock (mixinInfo)
293  {
294  if (mixinInfo.Mixin.TypeAnalysisStatus == AnalysisStatus.None)
295  {
296  mixinInfo.Mixin.TypeAnalysisStatus = AnalysisStatus.InProgress;
297 
298  // TODO: order + typedef
299  var typeAnalyzer = new ParadoxTypeAnalysis(new ParsingResult());
300  typeAnalyzer.Run(mixinInfo.MixinAst);
301  mixinInfo.Mixin.TypeAnalysisStatus = AnalysisStatus.Complete;
302  }
303  else if (mixinInfo.Mixin.TypeAnalysisStatus != AnalysisStatus.Complete)
304  {
305  throw new Exception("Type analysis failed for mixin " + mixinInfo.MixinName);
306  }
307  }
308  }
309 
310  /// <summary>
311  /// Build the virtual table for the specified mixin
312  /// </summary>
313  /// <param name="mixinInfo">the mixin</param>
314  private void BuildVirtualTables(ModuleMixinInfo mixinInfo)
315  {
316  var mixin = mixinInfo.Mixin;
317 
318  if (mixin.VirtualTableStatus == AnalysisStatus.Error || mixin.VirtualTableStatus == AnalysisStatus.Cyclic || mixin.VirtualTableStatus == AnalysisStatus.Complete)
319  return;
320 
321  if (!mixinInfo.Instanciated)
322  return;
323 
324  foreach (var dep in mixin.InheritanceList)
325  {
326  var depInfo = MixinInfos.FirstOrDefault(x => x.Mixin == dep);
327  BuildVirtualTables(depInfo);
328  }
329 
330  // merge the virtual tables
331  foreach (var dep in mixin.InheritanceList)
332  mixin.VirtualTable.MergeWithLocalVirtualTable(dep.LocalVirtualTable, mixin.MixinName, ErrorWarningLog);
333 
334  mixin.VirtualTable.MergeWithLocalVirtualTable(mixin.LocalVirtualTable, mixin.MixinName, ErrorWarningLog);
335 
336  foreach (var dep in mixin.InheritanceList)
337  mixin.VirtualTable.AddVirtualTable(dep.VirtualTable, dep.MixinName, ErrorWarningLog);
338  mixin.VirtualTable.AddFinalDeclarations(mixin.LocalVirtualTable.Methods.Select(x => x.Method).ToList(), mixin.MixinName, ErrorWarningLog);
339 
340  foreach (var variable in mixin.VirtualTable.Variables.Select(x => x.Variable))
341  {
342  if (mixin.VirtualTable.Variables.Any(x => x.Variable != variable && x.Variable.Name.Text == variable.Name.Text))
343  mixin.PotentialConflictingVariables.Add(variable);
344  }
345  foreach (var method in mixin.VirtualTable.Methods.Select(x => x.Method))
346  {
347  if (mixin.VirtualTable.Methods.Any(x => x.Method != method && x.Method.IsSameSignature(method)))
348  mixin.PotentialConflictingMethods.Add(method);
349  }
350 
351  CheckStageClass(mixin);
352 
353  mixin.VirtualTableStatus = AnalysisStatus.Complete;
354  }
355 
356  /// <summary>
357  /// Check if the class is stage
358  /// </summary>
359  /// <param name="mixin">the ModuleMixin to check</param>
360  private void CheckStageClass(ModuleMixin mixin)
361  {
362  mixin.StageOnlyClass = mixin.VirtualTable.Variables.All(x => x.Variable.Qualifiers.Contains(ParadoxStorageQualifier.Stage)) && mixin.VirtualTable.Methods.All(x => x.Method.Qualifiers.Contains(ParadoxStorageQualifier.Stage) && !x.Method.Qualifiers.Contains(ParadoxStorageQualifier.Clone));
363  }
364 
365  /// <summary>
366  /// Performs an semantic analysis of the mixin inside its own context
367  /// </summary>
368  /// <param name="mixinInfo">The mixin to analyze</param>
369  private void ModuleSemanticAnalysisPerMixin(ModuleMixinInfo mixinInfo)
370  {
371  if (mixinInfo == null)
372  return;
373 
374  var mixin = mixinInfo.Mixin;
375 
376  if (mixin.DependenciesStatus != AnalysisStatus.Complete || mixin.VirtualTableStatus != AnalysisStatus.Complete)
377  return;
378 
379  if (mixin.SemanticAnalysisStatus == AnalysisStatus.Complete)
380  return;
381  if (mixin.SemanticAnalysisStatus == AnalysisStatus.InProgress)
382  {
383  ErrorWarningLog.Error(ParadoxMessageCode.ErrorCyclicDependency, mixin.Shader.Span, mixin.Shader);
384  return;
385  }
386  if (!mixinInfo.Instanciated)
387  return;
388 
389  mixin.SemanticAnalysisStatus = AnalysisStatus.InProgress;
390 
391  // analyze the base mixins
392  foreach (var baseClass in mixin.BaseMixins)
393  {
394  var baseClassInfo = MixinInfos.FirstOrDefault(x => x.Mixin == baseClass);
395  ModuleSemanticAnalysisPerMixin(baseClassInfo);
396 
397  if (baseClassInfo.Mixin.SemanticAnalysisStatus == AnalysisStatus.Error || baseClassInfo.Mixin.SemanticAnalysisStatus == AnalysisStatus.Cyclic)
398  {
399  mixin.SemanticAnalysisStatus = AnalysisStatus.Error;
400  return;
401  }
402 
403  if (!baseClassInfo.Instanciated)
404  {
405  mixinInfo.Instanciated = false;
406  mixin.SemanticAnalysisStatus = AnalysisStatus.None;
407  return;
408  }
409 
410  if (mixin.LocalVirtualTable.CheckNameConflict(baseClass.VirtualTable, ErrorWarningLog))
411  {
412  mixin.SemanticAnalysisStatus = AnalysisStatus.Error;
413  return;
414  }
415  }
416 
417  // analyze the extern mixins
418  foreach (var externMixin in mixin.VariableDependencies)
419  {
420  var externMixinInfo = MixinInfos.FirstOrDefault(x => x.Mixin == externMixin.Value);
421  ModuleSemanticAnalysisPerMixin(externMixinInfo);
422  if (externMixinInfo.Mixin.SemanticAnalysisStatus == AnalysisStatus.Error || externMixinInfo.Mixin.SemanticAnalysisStatus == AnalysisStatus.Cyclic)
423  {
424  mixin.SemanticAnalysisStatus = AnalysisStatus.Error;
425  return;
426  }
427  }
428 
429  var compilationContext = MixinInfos.Select(x => x.Mixin).ToList();
430 
431  mixin.ParsingInfo = ParadoxSemanticAnalysis.RunAnalysis(mixin, compilationContext);
432 
433  var staticStageMixins = new List<ModuleMixin>();
434  staticStageMixins.AddRange(mixin.ParsingInfo.StaticReferences.VariablesReferences.Select(x => x.Key.GetTag(ParadoxTags.ShaderScope) as ModuleMixin));
435  staticStageMixins.AddRange(mixin.ParsingInfo.StaticReferences.MethodsReferences.Select(x => x.Key.GetTag(ParadoxTags.ShaderScope) as ModuleMixin));
436  staticStageMixins.AddRange(mixin.ParsingInfo.StageInitReferences.VariablesReferences.Select(x => x.Key.GetTag(ParadoxTags.ShaderScope) as ModuleMixin));
437  staticStageMixins.AddRange(mixin.ParsingInfo.StageInitReferences.MethodsReferences.Select(x => x.Key.GetTag(ParadoxTags.ShaderScope) as ModuleMixin));
438  staticStageMixins.RemoveAll(x => x == mixin);
439 
440  foreach (var dep in staticStageMixins)
441  {
442  var depInfo = MixinInfos.FirstOrDefault(x => x.Mixin == dep);
443  ModuleSemanticAnalysisPerMixin(depInfo);
444  if (dep.SemanticAnalysisStatus == AnalysisStatus.Error || dep.SemanticAnalysisStatus == AnalysisStatus.Cyclic)
445  {
446  mixin.SemanticAnalysisStatus = AnalysisStatus.Error;
447  return;
448  }
449  }
450 
451  // check the extern stage references (but do not change the type inference)
452  var externList = new List<ModuleMixin>();
453 
454  // NOTE: we cannot use the members .Values of .Keys because it internally modifies the dictionary, creating a Dictionary<K,T>.ValueCollection which cannot be cloned (no default constructor).
455  // This results in a exception in the DeepClone code.
456  mixin.InheritanceList.ForEach(dep => externList.AddRange(dep.VariableDependencies.Select(x => x.Value)));
457  externList.AddRange(mixin.VariableDependencies.Select(x => x.Value));
458  externList.ForEach(ext => CheckReferencesFromExternMixin(ext, mixin));
459 
460  mixin.ParsingInfo.ErrorsWarnings.CopyTo(ErrorWarningLog);
461 
462  mixin.SemanticAnalysisStatus = AnalysisStatus.Complete;
463  }
464 
465  /// <summary>
466  /// Check that the stage function calls are possible and that the stage declared variable have a correct type
467  /// </summary>
468  /// <param name="externMixin">the mixin to look into</param>
469  /// <param name="contextMixin">the root mixin</param>
470  private void CheckReferencesFromExternMixin(ModuleMixin externMixin, ModuleMixin contextMixin)
471  {
472  // test that the root mixin has the correct type
473  foreach (var variable in externMixin.ParsingInfo.StageInitializedVariables)
474  {
475  if (variable.Type.Name.Text != contextMixin.MixinName && contextMixin.InheritanceList.All(x => x.MixinName == variable.Type.Name.Text)) // since it is the same AST, compare the object?
476  ErrorWarningLog.Error(ParadoxMessageCode.ErrorExternStageVariableNotFound, variable.Span, variable, externMixin.MixinName);
477  }
478 
479  foreach (var stageCall in externMixin.ParsingInfo.StageMethodCalls)
480  {
481  var decl = contextMixin.FindTopThisFunction(stageCall).FirstOrDefault();
482  if (decl == null)
483  ErrorWarningLog.Error(ParadoxMessageCode.ErrorExternStageFunctionNotFound, stageCall.Span, stageCall, externMixin.MixinName, contextMixin.MixinName);
484  }
485 
486  // recursive calls
487  foreach (var mixin in externMixin.InheritanceList)
488  {
489  CheckReferencesFromExternMixin(mixin, contextMixin);
490 
491  foreach (var externModule in mixin.VariableDependencies)
492  CheckReferencesFromExternMixin(externModule.Value, contextMixin);
493  }
494 
495  foreach (var externModule in externMixin.VariableDependencies)
496  CheckReferencesFromExternMixin(externModule.Value, contextMixin);
497  }
498 
499  #endregion
500 
501  #region Private static methods
502 
503  /// <summary>
504  /// Tests the equality of the macro sets.
505  /// </summary>
506  /// <param name="macros0">The first set of macros.</param>
507  /// <param name="macros1">The second set of macros.</param>
508  /// <returns>True if the sets match, false otherwise.</returns>
509  private static bool AreMacrosEqual(SiliconStudio.Shaders.Parser.ShaderMacro[] macros0, SiliconStudio.Shaders.Parser.ShaderMacro[] macros1)
510  {
511  return macros0.All(macro => macros1.Any(x => x.Name == macro.Name && x.Definition == macro.Definition)) && macros1.All(macro => macros0.Any(x => x.Name == macro.Name && x.Definition == macro.Definition));
512  }
513 
514  #endregion
515  }
516 
517  public class VTableReference
518  {
519  public string Shader = "";
520 
521  public int Slot = -1;
522 
523  public override bool Equals(object obj)
524  {
525  var vtr = obj as VTableReference;
526  if (vtr == null)
527  return false;
528 
529  return Slot == vtr.Slot && Shader == vtr.Shader;
530  }
531 
532  public override int GetHashCode()
533  {
534  return Slot;
535  //return (Shader.GetHashCode() * 397) ^ (Slot + 2);
536  }
537  }
538 }
A class to collect parsing/expression messages.
Definition: LoggerResult.cs:13
A variable declaration.
Definition: Variable.cs:11
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
Toplevel container of a shader parsing result.
Definition: Shader.cs:12
static readonly SiliconStudio.Shaders.Ast.StorageQualifier Clone
Clone keyword (clone).
AnalysisStatus
A status needed to analyze the mixin in the correct order within a compilation module ...
Definition: ModuleMixin.cs:276