File: Writers\CSharp\CSDeclarationWriter.Types.cs
Web Access
Project: src\src\Microsoft.Cci.Extensions\Microsoft.Cci.Extensions.csproj (Microsoft.Cci.Extensions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Cci.Extensions;
using Microsoft.Cci.Extensions.CSharp;
 
namespace Microsoft.Cci.Writers.CSharp
{
    public partial class CSDeclarationWriter
    {
        public void WriteTypeDeclaration(ITypeDefinition type)
        {
            INamedTypeDefinition namedType = (INamedTypeDefinition)type;
 
            WriteAttributes(type.Attributes);
            WriteAttributes(type.SecurityAttributes);
 
            //TODO: We should likely add support for the SerializableAttribute something like:
            // if (type.IsSerializable) WriteFakeAttribute("System.Serializable");
            // But we need also consider if this attribute is filtered out or not but I guess
            // we have the same problem with all the fake attributes at this point.
 
            if ((type.IsStruct || type.IsClass) && type.Layout != LayoutKind.Auto)
            {
                FakeCustomAttribute structLayout = new FakeCustomAttribute("System.Runtime.InteropServices", "StructLayoutAttribute");
                string layoutKind = string.Format("System.Runtime.InteropServices.LayoutKind.{0}", type.Layout.ToString());
 
                if (_forCompilationIncludeGlobalprefix)
                    layoutKind = "global::" + layoutKind;
 
                var args = new List<string>();
                args.Add(layoutKind);
 
                if (type.SizeOf != 0)
                {
                    string sizeOf = string.Format("Size={0}", type.SizeOf);
                    args.Add(sizeOf);
                }
 
                if (type.Alignment != 0)
                {
                    string pack = string.Format("Pack={0}", type.Alignment);
                    args.Add(pack);
                }
 
                if (type.StringFormat != StringFormatKind.Ansi)
                {
                    string charset = string.Format("CharSet={0}System.Runtime.InteropServices.CharSet.{1}", _forCompilationIncludeGlobalprefix ? "global::" : "", type.StringFormat);
                    args.Add(charset);
                }
 
                if (IncludeAttribute(structLayout))
                    WriteFakeAttribute(structLayout.FullTypeName, args.ToArray());
            }
 
            WriteVisibility(TypeHelper.TypeVisibilityAsTypeMemberVisibility(type));
 
            IMethodDefinition invoke = type.GetInvokeMethod();
            if (invoke != null)
            {
                Contract.Assert(type.IsDelegate);
 
                byte? nullableContextValue = invoke.Attributes.GetCustomAttributeArgumentValue<byte?>(CSharpCciExtensions.NullableContextAttributeFullName);
                if (invoke.IsMethodUnsafe()) WriteKeyword("unsafe");
                WriteKeyword("delegate");
                WriteTypeName(invoke.Type, invoke.ReturnValueAttributes, methodNullableContextValue: nullableContextValue);
                WriteIdentifier(namedType.Name);
                if (type.IsGeneric) WriteGenericParameters(type.GenericParameters);
                WriteParameters(invoke.Parameters, invoke.ContainingType, nullableContextValue);
                if (type.IsGeneric) WriteGenericContraints(type.GenericParameters, TypeNullableContextValue); // Delegates are special, and the NullableContextValue we should fallback to is the delegate type one, not the invoke method one.
                WriteSymbol(";");
            }
            else
            {
                WriteTypeModifiers(type);
                WriteIdentifier(namedType.Name);
                Contract.Assert(!(type is IGenericTypeInstance), "Currently don't support generic type instances if we hit this then add support");
                if (type.IsGeneric) WriteGenericParameters(type.GenericParameters);
                WriteBaseTypes(type);
                if (type.IsGeneric) WriteGenericContraints(type.GenericParameters);
 
                if (type.IsEnum)
                    WriteEnumType(type);
            }
        }
 
        // Note that the metadata order for interfaces may change from one release to another.
        // This isn't an incompatibility in surface area.  So, we must sort our list of base types
        // to reflect this.
        private void WriteBaseTypes(ITypeDefinition type)
        {
            ITypeReference baseType = GetBaseType(type);
            IEnumerable<ITypeReference> interfaces = type.Interfaces.Where(IncludeBaseType).OrderBy(t => GetTypeName(t), StringComparer.OrdinalIgnoreCase);
 
            if (baseType == null && !interfaces.Any())
                return;
 
            WriteSpace();
            WriteSymbol(":", true);
 
            if (baseType != null)
            {
                WriteTypeName(baseType, type.Attributes, noSpace: true);
                if (interfaces.Any())
                {
                    WriteSymbol(",", addSpace: true);
                }
            }
 
            WriteList(GetInterfaceWriterActions(type, interfaces), i => i());
        }
 
        private IEnumerable<Action> GetInterfaceWriterActions(ITypeReference type, IEnumerable<ITypeReference> interfaces)
        {
            if (interfaces.Any())
            {
                string location = type.Locations.FirstOrDefault()?.Document?.Location;
                uint typeToken = ((IMetadataObjectWithToken)type).TokenValue;
                foreach (var interfaceImplementation in interfaces)
                {
                    object nullableAttributeValue = null;
                    if (location != null)
                    {
                        nullableAttributeValue = interfaceImplementation.GetInterfaceImplementationAttributeConstructorArgument(typeToken, location, _metadataReaderCache, CSharpCciExtensions.NullableConstructorArgumentParser);
                    }
 
                    yield return () => WriteTypeName(interfaceImplementation, noSpace: true, nullableAttributeArgument: nullableAttributeValue);
                }
            }
        }
 
        private string GetTypeName(ITypeReference type)
        {
            NameFormattingOptions formattingOptions = NameFormattingOptions.TypeParameters | NameFormattingOptions.UseTypeKeywords;
 
            if (!_forCompilation)
            {
                formattingOptions |= NameFormattingOptions.OmitContainingNamespace;
            }
 
            return GetTypeName(type, formattingOptions);
        }
 
        private string GetTypeName(ITypeReference type, NameFormattingOptions formattingOptions)
        {
            if ((formattingOptions & NameFormattingOptions.UseTypeKeywords) != 0)
            {
                if (TypeHelper.TypesAreEquivalent(type, type.PlatformType.SystemIntPtr))
                {
                    if ((LangVersion >= LangVersion11_0) || type.Attributes.HasNativeIntegerAttribute())
                    {
                        return "nint";
                    }
                }
                else if (TypeHelper.TypesAreEquivalent(type, type.PlatformType.SystemUIntPtr))
                {
                    if ((LangVersion >= LangVersion11_0) || type.Attributes.HasNativeIntegerAttribute())
                    {
                        return "nuint";
                    }
                }
            }
            return TypeHelper.GetTypeName(type, formattingOptions);
        }
 
        private ITypeReference GetBaseType(ITypeDefinition type)
        {
            if (type == Dummy.Type)
                return null;
 
            ITypeReference baseTypeRef = type.BaseClasses.FirstOrDefault();
 
            if (baseTypeRef == null)
                return null;
 
            if (baseTypeRef.IsDefaultCSharpBaseType(type))
                return null;
 
            if (!IncludeBaseType(baseTypeRef))
            {
                return GetBaseType(baseTypeRef.ResolvedType);
            }
 
            return baseTypeRef;
        }
 
        private void WriteTypeModifiers(ITypeDefinition type)
        {
            if (type.GetHiddenBaseType(_filter) != Dummy.Type)
            {
                WriteKeyword("new");
            }
 
            if (type.IsDelegate)
                throw new NotSupportedException("This method doesn't support delegates!");
            else if (type.IsEnum)
                WriteKeyword("enum");
            else if (type.IsValueType)
            {
                if (type.Attributes.HasIsReadOnlyAttribute())
                    WriteKeyword("readonly");
 
                if (type.Attributes.HasIsByRefLikeAttribute())
                    WriteKeyword("ref");
 
                WritePartialKeyword();
                WriteKeyword("struct");
            }
            else if (type.IsInterface)
            {
                WritePartialKeyword();
                WriteKeyword("interface");
            }
            else
            {
                if (!type.IsClass)
                    throw new NotSupportedException("Don't understand what kind of type this is!");
 
                if (type.IsStatic)
                    WriteKeyword("static");
                else if (type.IsSealed)
                    WriteKeyword("sealed");
                else if (type.IsAbstract)
                    WriteKeyword("abstract");
 
                WritePartialKeyword();
                WriteKeyword("class");
            }
        }
 
        private void WritePartialKeyword()
        {
            if (_forCompilation)
                WriteKeyword("partial");
        }
 
        private void WriteEnumType(ITypeDefinition type)
        {
            ITypeReference enumType = type.GetEnumType();
 
            // Don't write the default type
            if (TypeHelper.TypesAreEquivalent(enumType, type.PlatformType.SystemInt32))
                return;
 
            WriteSpace();
            WriteSymbol(":", addSpace: true);
            WriteTypeName(enumType, noSpace: true);
        }
 
        private bool IncludeBaseType(ITypeReference iface)
        {
            ITypeDefinition ifaceType = iface.ResolvedType;
 
            // We should by default include base types even if we cannot resolve
            // for cases where we are working with standalone assemblies.
            if (ifaceType == Dummy.Type)
                return true;
 
            return _alwaysIncludeBase || _filter.Include(ifaceType);
        }
    }
}