File: Writers\CSharp\CSDeclarationWriter.Methods.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.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Cci.Extensions;
using Microsoft.Cci.Extensions.CSharp;
using Microsoft.Cci.Filters;
using Microsoft.Cci.Writers.Syntax;
 
namespace Microsoft.Cci.Writers.CSharp
{
    public partial class CSDeclarationWriter
    {
        private void WriteMethodDefinition(IMethodDefinition method)
        {
            if (method.IsPropertyOrEventAccessor())
                return;
 
            WriteMethodPseudoCustomAttributes(method);
 
            WriteAttributes(method.Attributes);
            WriteAttributes(method.SecurityAttributes);
            WriteAttributes(method.ReturnValueAttributes, prefix: "return");
 
            if (method.IsDestructor())
            {
                // If platformNotSupportedExceptionMessage is != null we're generating a dummy assembly which means we don't need a destructor at all.
                if (_platformNotSupportedExceptionMessage == null)
                    WriteDestructor(method);
 
                return;
            }
 
            var writeVisibility = true;
 
            if (method.ContainingTypeDefinition.IsInterface)
            {
                writeVisibility = false;
            }
            
            if (method.IsExplicitInterfaceMethod() || method.IsStaticConstructor)
            {
                writeVisibility = false;
            }
 
            if (writeVisibility)
            {
                WriteVisibility(method.Visibility);
            }
 
            WriteMethodModifiers(method);
 
            WriteInterfaceMethodModifiers(method);
            WriteMethodDefinitionSignature(method);
            WriteMethodBody(method);
        }
 
        private void WriteDestructor(IMethodDefinition method)
        {
            WriteSymbol("~");
            WriteIdentifier(((INamedEntity)method.ContainingTypeDefinition).Name);
            WriteSymbol("(");
            WriteSymbol(")", false);
            WriteEmptyBody();
        }
 
 
        private void WriteTypeName(ITypeReference type, ITypeReference containingType, IEnumerable<ICustomAttribute> attributes = null, byte? methodNullableContextValue = null)
        {
            WriteTypeName(type, attributes: attributes, methodNullableContextValue: methodNullableContextValue);
        }
 
        private string GetNormalizedMethodName(IName name)
        {
            switch (name.Value)
            {
                case "op_Decrement": return "operator --";
                case "op_Increment": return "operator ++";
                case "op_UnaryNegation": return "operator -";
                case "op_UnaryPlus": return "operator +";
                case "op_LogicalNot": return "operator !";
                case "op_True": return "operator true";
                case "op_False": return "operator false";
                case "op_OnesComplement": return "operator ~";
                case "op_Addition": return "operator +";
                case "op_Subtraction": return "operator -";
                case "op_Multiply": return "operator *";
                case "op_Division": return "operator /";
                case "op_Modulus": return "operator %";
                case "op_ExclusiveOr": return "operator ^";
                case "op_BitwiseAnd": return "operator &";
                case "op_BitwiseOr": return "operator |";
                case "op_LeftShift": return "operator <<";
                case "op_RightShift": return "operator >>";
                case "op_UnsignedRightShift": return "operator >>>";
                case "op_Equality": return "operator ==";
                case "op_GreaterThan": return "operator >";
                case "op_LessThan": return "operator <";
                case "op_Inequality": return "operator !=";
                case "op_GreaterThanOrEqual": return "operator >=";
                case "op_LessThanOrEqual": return "operator <=";
                case "op_Explicit": return "explicit operator";
                case "op_Implicit": return "implicit operator";
                case "op_CheckedDecrement": return "operator checked --";
                case "op_CheckedIncrement": return "operator checked ++";
                case "op_CheckedUnaryNegation": return "operator checked -";
                case "op_CheckedAddition": return "operator checked +";
                case "op_CheckedSubtraction": return "operator checked -";
                case "op_CheckedMultiply": return "operator checked *";
                case "op_CheckedDivision": return "operator checked /";
                case "op_CheckedExplicit": return "explicit operator checked";
                default: return name.Value; // return just the name
            }
        }
 
        private void WriteMethodName(IMethodDefinition method)
        {
            if (method.IsConstructor || method.IsStaticConstructor)
            {
                INamedEntity named = method.ContainingTypeDefinition.UnWrap() as INamedEntity;
                if (named != null)
                {
                    WriteIdentifier(named.Name.Value);
                    return;
                }
            }
 
            if (method.IsExplicitInterfaceMethod())
            {
                IMethodImplementation methodImplementation = method.GetMethodImplementation();
                object nullableAttributeArgument = methodImplementation.GetExplicitInterfaceMethodNullableAttributeArgument(_metadataReaderCache);
 
                WriteTypeName(methodImplementation.ImplementedMethod.ContainingType, noSpace: true, nullableAttributeArgument: nullableAttributeArgument);
                WriteSymbol(".");
                WriteIdentifier(GetNormalizedMethodName(methodImplementation.ImplementedMethod.Name));
            }
            else
            {
                WriteIdentifier(GetNormalizedMethodName(method.Name));
            }
        }
 
        private void WriteMethodDefinitionSignature(IMethodDefinition method)
        {
            byte? nullableContextValue = method.Attributes.GetCustomAttributeArgumentValue<byte?>(CSharpCciExtensions.NullableContextAttributeFullName);
            bool isOperator = method.IsConversionOperator();
 
            if (!isOperator && !method.IsConstructor && !method.IsStaticConstructor)
            {
                if (method.Attributes.HasIsReadOnlyAttribute() && (LangVersion >= LangVersion8_0))
                {
                    WriteKeyword("readonly");
                }
 
                if (method.ReturnValueIsByRef)
                {
                    WriteKeyword("ref");
 
                    if (method.ReturnValueAttributes.HasIsReadOnlyAttribute())
                        WriteKeyword("readonly");
                }
 
                // We are ignoring custom modifiers right now, we might need to add them later.
                WriteTypeName(method.Type, method.ContainingType, method.ReturnValueAttributes, nullableContextValue);
            }
 
            if (method.IsExplicitInterfaceMethod() && _forCompilationIncludeGlobalprefix)
                Write("global::");
 
            WriteMethodName(method);
 
            if (isOperator)
            {
                WriteSpace();
 
                WriteTypeName(method.Type, method.ContainingType, methodNullableContextValue: nullableContextValue);
            }
 
            Contract.Assert(method is not IGenericMethodInstance, "Currently don't support generic method instances");
            if (method.IsGeneric)
                WriteGenericParameters(method.GenericParameters);
 
            WriteParameters(method.Parameters, method.ContainingType, nullableContextValue, extensionMethod: method.IsExtensionMethod(), acceptsExtraArguments: method.AcceptsExtraArguments);
            if (method.IsGeneric && !method.IsOverride() && !method.IsExplicitInterfaceMethod())
                WriteGenericContraints(method.GenericParameters, nullableContextValue);
        }
 
        private void WriteParameters(IEnumerable<IParameterDefinition> parameters, ITypeReference containingType, byte? methodNullableContextValue, bool property = false, bool extensionMethod = false, bool acceptsExtraArguments = false)
        {
            string start = property ? "[" : "(";
            string end = property ? "]" : ")";
 
            WriteSymbol(start);
            _writer.WriteList(parameters, p =>
            {
                WriteParameter(p, containingType, extensionMethod, methodNullableContextValue);
                extensionMethod = false;
            });
 
            if (acceptsExtraArguments)
            {
                if (parameters.Any())
                    _writer.WriteSymbol(",");
                _writer.WriteSpace();
                _writer.Write("__arglist");
            }
 
            WriteSymbol(end);
        }
 
        private void WriteParameter(IParameterDefinition parameter, ITypeReference containingType, bool extensionMethod, byte? methodNullableContextValue)
        {
            WriteAttributes(parameter.Attributes, true);
 
            if (extensionMethod)
                WriteKeyword("this");
 
            if (parameter.IsParameterArray)
                WriteKeyword("params");
 
            if (parameter.IsOut && !parameter.IsIn && parameter.IsByReference)
            {
                WriteKeyword("out");
            }
            else
            {
                // For In/Out we should not emit them until we find a scenario that is needs them.
                //if (parameter.IsIn)
                //   WriteFakeAttribute("System.Runtime.InteropServices.In", writeInline: true);
                //if (parameter.IsOut)
                //    WriteFakeAttribute("System.Runtime.InteropServices.Out", writeInline: true);
                if (parameter.IsByReference)
                {
                    if (parameter.Attributes.HasIsReadOnlyAttribute())
                    {
                        WriteKeyword("in");
                    }
                    else
                    {
                        WriteKeyword("ref");
                    }
                }
            }
 
            WriteTypeName(parameter.Type, containingType, parameter.Attributes, methodNullableContextValue);
            WriteIdentifier(parameter.Name);
            if (parameter.IsOptional && parameter.HasDefaultValue)
            {
                WriteSymbol(" = ");
                WriteMetadataConstant(parameter.DefaultValue, parameter.Type);
            }
        }
 
        private void WriteInterfaceMethodModifiers(IMethodDefinition method)
        {
            if (method.GetHiddenBaseMethod(_filter) != Dummy.Method)
                WriteKeyword("new");
        }
 
        private void WriteMethodModifiers(IMethodDefinition method)
        {
            if (method.IsMethodUnsafe() ||
                (method.IsConstructor && IsBaseConstructorCallUnsafe(method.ContainingTypeDefinition)))
            {
                WriteKeyword("unsafe");
            }
 
            if (method.IsStatic)
                WriteKeyword("static");
 
            if (method.IsPlatformInvoke)
                WriteKeyword("extern");
 
            if (method.IsVirtual)
            {
                if (method.ContainingTypeDefinition.IsInterface)
                {
                    if (method.IsStatic && method.IsAbstract)
                    {
                        WriteKeyword("abstract");
                    }
                }
                else if (method.IsNewSlot)
                {
                    if (method.IsAbstract)
                        WriteKeyword("abstract");
                    else if (!method.IsSealed) // non-virtual interfaces implementations are sealed virtual newslots
                        WriteKeyword("virtual");
                }
                else
                {
                    if (method.IsAbstract)
                        WriteKeyword("abstract");
                    else if (method.IsSealed)
                        WriteKeyword("sealed");
                    WriteKeyword("override");
                }
            }
        }
 
        private void WriteMethodBody(IMethodDefinition method)
        {
            if (method.IsAbstract || !_forCompilation || method.IsPlatformInvoke)
            {
                WriteSymbol(";");
                return;
            }
 
            if (method.IsConstructor)
                WriteBaseConstructorCall(method.ContainingTypeDefinition);
 
            // Write Dummy Body
            WriteSpace();
            WriteSymbol("{", true);
 
            if (_platformNotSupportedExceptionMessage != null && !method.IsDispose())
            {
                Write("throw new ");
                if (_forCompilationIncludeGlobalprefix)
                    Write("global::");
 
                Write("System.PlatformNotSupportedException(");
 
                if (_platformNotSupportedExceptionMessage.StartsWith("SR."))
                {
                    if (_forCompilationIncludeGlobalprefix)
                        Write("global::");
                    Write($"System.{ _platformNotSupportedExceptionMessage}");
                }
                else if (_platformNotSupportedExceptionMessage.Length > 0)
                    Write($"\"{_platformNotSupportedExceptionMessage}\"");
 
                Write("); ");
            }
            else if (NeedsMethodBodyForCompilation(method))
            {
                Write("throw null; ");
            }
 
            WriteSymbol("}");
        }
 
        private bool NeedsMethodBodyForCompilation(IMethodDefinition method)
        {
            // Structs cannot have empty constructors so we need a body
            if (method.ContainingTypeDefinition.IsValueType && method.IsConstructor)
                return true;
 
            // Compiler requires out parameters to be initialized
            if (method.Parameters.Any(p => p.IsOut))
                return true;
 
            // For non-void returning methods we need a body.
            if (!TypeHelper.TypesAreEquivalent(method.Type, method.ContainingTypeDefinition.PlatformType.SystemVoid))
                return true;
 
            return false;
        }
 
        private void WritePrivateConstructor(ITypeDefinition type)
        {
            if (!_forCompilation ||
                type.IsInterface ||
                type.IsEnum ||
                type.IsDelegate ||
                type.IsValueType ||
                type.IsStatic)
                return;
 
            var visibility = Filter switch
            {
                IncludeAllFilter _ => TypeMemberVisibility.Private,
                InternalsAndPublicCciFilter _ => TypeMemberVisibility.Private,
                IntersectionFilter intersection => intersection.Filters.Any(
                        f => f is IncludeAllFilter || f is InternalsAndPublicCciFilter) ?
                    TypeMemberVisibility.Private :
                    TypeMemberVisibility.Assembly,
                _ => TypeMemberVisibility.Assembly
            };
 
            WriteVisibility(visibility);
            if (IsBaseConstructorCallUnsafe(type))
            {
                WriteKeyword("unsafe");
            }
 
            WriteIdentifier(((INamedEntity)type).Name);
            WriteSymbol("(");
            WriteSymbol(")");
            WriteBaseConstructorCall(type);
            WriteEmptyBody();
        }
 
        private void WriteBaseConstructorCall(ITypeDefinition type)
        {
            var ctor = GetBaseConstructorForCall(type);
            if (ctor == null)
                return;
 
            WriteSpace();
            WriteSymbol(":", true);
            WriteKeyword("base");
            WriteSymbol("(");
            _writer.WriteList(ctor.Parameters, p => WriteDefaultOf(p.Type, ShouldSuppressNullCheck()));
            WriteSymbol(")");
        }
 
        private bool IsBaseConstructorCallUnsafe(ITypeDefinition type)
        {
            var constructor = GetBaseConstructorForCall(type);
            if (constructor == null)
            {
                return false;
            }
 
            foreach (var parameter in constructor.Parameters)
            {
                if (parameter.Type.IsUnsafeType())
                {
                    return true;
                }
            }
 
            return false;
        }
 
        private IMethodDefinition GetBaseConstructorForCall(ITypeDefinition type)
        {
            if (!_forCompilation)
            {
                // No need to generate a call to a base constructor.
                return null;
            }
 
            var baseType = type.BaseClasses.FirstOrDefault().GetDefinitionOrNull();
            if (baseType == null)
            {
                // No base type to worry about.
                return null;
            }
 
            var constructors = baseType.Methods.Where(
                m => m.IsConstructor && _filter.Include(m) && !m.Attributes.Any(a => a.IsObsoleteWithUsageTreatedAsCompilationError()));
 
            if (constructors.Any(c => c.ParameterCount == 0))
            {
                // Don't need a base call if base class has a default constructor.
                return null;
            }
 
            return constructors.FirstOrDefault();
        }
 
        /// <summary>
        /// When generated .notsupported.cs files, we need to generate calls to the base constructor.
        /// However, if the base constructor doesn't accept null, passing default(T) will cause a compile
        /// error. In this case, suppress the null check.
        /// NOTE: It was deemed too much work to dynamically check if the base constructor accepts null
        /// or not, until we update GenAPI to be based on Roslyn instead of CCI. For now, just always
        /// suppress the null check.
        /// </summary>
        private bool ShouldSuppressNullCheck() =>
            LangVersion >= LangVersion8_0 &&
            _platformNotSupportedExceptionMessage != null;
 
        private void WriteEmptyBody()
        {
            if (!_forCompilation)
            {
                WriteSymbol(";");
            }
            else
            {
                WriteSpace();
                WriteSymbol("{", true);
                WriteSymbol("}");
            }
        }
 
        private void WriteDefaultOf(ITypeReference type, bool suppressNullCheck = false)
        {
            WriteKeyword("default", true);
            WriteSymbol("(");
            WriteTypeName(type, noSpace: true);
            WriteSymbol(")");
 
            if (suppressNullCheck && !type.IsValueType)
            {
                WriteSymbol("!");
            }
        }
 
        public static IDefinition GetDummyConstructor(ITypeDefinition type)
        {
            return new DummyInternalConstructor() { ContainingType = type };
        }
 
        private class DummyInternalConstructor : IDefinition
        {
            public ITypeDefinition ContainingType { get; set; }
 
            public IEnumerable<ICustomAttribute> Attributes
            {
                get { throw new System.NotImplementedException(); }
            }
 
            public void Dispatch(IMetadataVisitor visitor)
            {
                throw new System.NotImplementedException();
            }
 
            public IEnumerable<ILocation> Locations
            {
                get { throw new System.NotImplementedException(); }
            }
 
            public void DispatchAsReference(IMetadataVisitor visitor)
            {
                throw new System.NotImplementedException();
            }
        }
    }
}