/// <summary> /// Enclosing declaration is optional when we don't want to get qualified name in the enclosing declaration scope /// Meaning needs to be specified if the enclosing declaration is given /// </summary> private void BuildSymbolDisplay(ISymbol inputSymbol, ISymbolWriter writer, INode enclosingDeclaration = null, SymbolFlags inputMeaning = SymbolFlags.None, SymbolFormatFlags flags = SymbolFormatFlags.None, TypeFormatFlags typeFlags = TypeFormatFlags.None) { Contract.Assert(inputSymbol != null); Contract.Assert(writer != null); // HINT: To simplify migration we're using "local" functions via delegates. // This code could be changed in the future, once C# would have local functions. Action <ISymbol, SymbolFlags> walkSymbol = null; ISymbol parentSymbol = null; Action <ISymbol> appendParentTypeArgumentsAndSymbolName = null; appendParentTypeArgumentsAndSymbolName = (ISymbol symbol) => { if (parentSymbol != null) { // Write type arguments of instantiated class/interface here if ((flags & SymbolFormatFlags.WriteTypeParametersOrArguments) != SymbolFormatFlags.None) { if ((symbol.Flags & SymbolFlags.Instantiated) != SymbolFlags.None) { // TODO: check types to avoid redundant ToArray call. BuildDisplayForTypeArgumentsAndDelimiters( m_checker.GetTypeParametersOfClassOrInterface(parentSymbol), ((ITransientSymbol)symbol).Mapper, writer, enclosingDeclaration); } else { BuildTypeParameterDisplayFromSymbol(parentSymbol, writer, enclosingDeclaration); } } WritePunctuation(writer, SyntaxKind.DotToken); } parentSymbol = symbol; AppendSymbolNameOnly(symbol, writer); }; // Let the writer know we just wrote out a symbol. The declaration emitter writer uses // this to determine if an import it has previously seen (and not written out) needs // to be written to the file once the walk of the tree is complete. // // NOTE(cyrusn): This approach feels somewhat unfortunate. A simple pass over the tree // up front (for example, during checking) could determine if we need to emit the imports // and we could then access that data during declaration emit. writer.TrackSymbol(inputSymbol, enclosingDeclaration, inputMeaning); walkSymbol = (ISymbol symbol, SymbolFlags meaning) => { if (symbol != null) { var accessibleSymbolChain = m_checker.GetAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, (flags & SymbolFormatFlags.UseOnlyExternalAliasing) != SymbolFormatFlags.None); if (accessibleSymbolChain == null || m_checker.NeedsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.Count == 1 ? meaning : GetQualifiedLeftMeaning(meaning))) { // Go up and add our parent. walkSymbol( m_checker.GetParentOfSymbol(accessibleSymbolChain != null ? accessibleSymbolChain[0] : symbol), GetQualifiedLeftMeaning(meaning)); } if (accessibleSymbolChain != null) { foreach (var accessibleSymbol in accessibleSymbolChain) { appendParentTypeArgumentsAndSymbolName(accessibleSymbol); } } else { // If we didn't find accessible symbol chain for this symbol, break if this is external module if (parentSymbol == null && symbol.DeclarationList.Any(n => HasExternalModuleSymbol(n))) { return; } // if this is anonymous type break if ((symbol.Flags & SymbolFlags.TypeLiteral) != SymbolFlags.None || (symbol.Flags & SymbolFlags.ObjectLiteral) != SymbolFlags.None) { return; } appendParentTypeArgumentsAndSymbolName(symbol); } } }; // Get qualified name if the symbol is not a type parameter // and there is an enclosing declaration or we specifically // asked for it var isTypeParameter = inputSymbol.Flags & SymbolFlags.TypeParameter; var typeFormatFlag = TypeFormatFlags.UseFullyQualifiedType & typeFlags; if (isTypeParameter == SymbolFlags.None && (enclosingDeclaration != null || typeFormatFlag != TypeFormatFlags.None)) { walkSymbol(inputSymbol, inputMeaning); return; } appendParentTypeArgumentsAndSymbolName(inputSymbol); }