File: Internal\TypeSystem\RuntimeNoMetadataType.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.TypeLoader\src\System.Private.TypeLoader.csproj (System.Private.TypeLoader)
// 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.Reflection.Runtime.General;
using System.Text;

using Internal.Metadata.NativeFormat;
using Internal.NativeFormat;
using Internal.Runtime;
using Internal.Runtime.Augments;
using Internal.Runtime.TypeLoader;
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;

namespace Internal.TypeSystem.NoMetadata
{
    /// <summary>
    /// Type that once had metadata, but that metadata is not available
    /// for the lifetime of the TypeSystemContext. Directly correlates
    /// to a RuntimeTypeHandle useable in the current environment.
    /// This type replaces the placeholder NoMetadataType that comes
    /// with the common type system codebase
    /// </summary>
    internal class NoMetadataType : DefType
    {
        private TypeSystemContext _context;
        private int _hashcode;
        private RuntimeTypeHandle _genericTypeDefinition;
        private DefType _genericTypeDefinitionAsDefType;
        private Instantiation _instantiation;

        // "_baseType == this" means "base type was not initialized yet"
        private DefType _baseType;

        public unsafe NoMetadataType(TypeSystemContext context, RuntimeTypeHandle genericTypeDefinition, int instantiationLength, ReadOnlySpan<Runtime.GenericVariance> runtimeVarianceData, int hashcode)
        {
            TypeDesc[] genericParameters;
            if (instantiationLength == 0)
            {
                genericParameters = Array.Empty<TypeDesc>();
            }
            else
            {
                genericParameters = new TypeDesc[instantiationLength];
                for (int i = 0; i < genericParameters.Length; i++)
                {
                    GenericVariance variance = runtimeVarianceData.Length == 0 ? GenericVariance.None : runtimeVarianceData[i] switch
                    {
                        Runtime.GenericVariance.Contravariant => GenericVariance.Contravariant,
                        Runtime.GenericVariance.Covariant => GenericVariance.Covariant,
                        Runtime.GenericVariance.NonVariant or Runtime.GenericVariance.ArrayCovariant => GenericVariance.None,
                        _ => throw new NotImplementedException()
                    };
                    genericParameters[i] = new RuntimeGenericParameterDesc(GenericParameterKind.Type, i, this, variance);
                }
            }

            Instantiation instantiation = new Instantiation(genericParameters);
            Init(context, genericTypeDefinition, null, instantiation, hashcode);
        }

        public unsafe NoMetadataType(TypeSystemContext context, RuntimeTypeHandle genericTypeDefinition, DefType genericTypeDefinitionAsDefType, Instantiation instantiation, int hashcode)
        {
            Init(context, genericTypeDefinition, genericTypeDefinitionAsDefType, instantiation, hashcode);
        }

        private void Init(TypeSystemContext context, RuntimeTypeHandle genericTypeDefinition, DefType genericTypeDefinitionAsDefType, Instantiation instantiation, int hashcode)
        {
            _hashcode = hashcode;
            _context = context;
            _genericTypeDefinition = genericTypeDefinition;
            _genericTypeDefinitionAsDefType = genericTypeDefinitionAsDefType;
            _genericTypeDefinitionAsDefType ??= this;

            _instantiation = instantiation;

            // Instantiation must either be:
            // Something valid (if the type is generic, or a generic type definition)
            // or Empty (if the type isn't a generic of any form)
            unsafe
            {
                Debug.Assert(((_instantiation.Length > 0) && _genericTypeDefinition.ToEETypePtr()->IsGenericTypeDefinition) ||
                             ((_instantiation.Length == 0) && !_genericTypeDefinition.ToEETypePtr()->IsGenericTypeDefinition));
            }

            // Base type is not initialized
            _baseType = this;
        }

        public override int GetHashCode()
        {
            return _hashcode;
        }

        public override TypeSystemContext Context
        {
            get
            {
                return _context;
            }
        }

        public override DefType BaseType
        {
            get
            {
                // _baseType == this means we didn't initialize it yet
                if (_baseType != this)
                    return _baseType;

                if (RetrieveRuntimeTypeHandleIfPossible())
                {
                    RuntimeTypeHandle baseTypeHandle;
                    if (!RuntimeAugments.TryGetBaseType(RuntimeTypeHandle, out baseTypeHandle))
                    {
                        Debug.Assert(false);
                    }

                    DefType baseType = !baseTypeHandle.IsNull() ? (DefType)Context.ResolveRuntimeTypeHandle(baseTypeHandle) : null;
                    SetBaseType(baseType);

                    return baseType;
                }
                else
                {
                    // Parsing of the base type has not yet happened. Perform that part of native layout parsing
                    // just-in-time
                    TypeBuilderState state = GetOrCreateTypeBuilderState();

                    ComputeTemplate();
                    NativeParser typeInfoParser = state.GetParserForNativeLayoutInfo();
                    NativeParser baseTypeParser = typeInfoParser.GetParserForBagElementKind(BagElementKind.BaseType);

                    ParseBaseType(state.NativeLayoutInfo.LoadContext, baseTypeParser);
                    Debug.Assert(_baseType != this);
                    return _baseType;
                }
            }
        }

        internal override void ParseBaseType(NativeLayoutInfoLoadContext nativeLayoutInfoLoadContext, NativeParser baseTypeParser)
        {
            if (!baseTypeParser.IsNull)
            {
                // If the base type is available from the native layout info use it if the type we have is a NoMetadataType
                SetBaseType((DefType)nativeLayoutInfoLoadContext.GetType(ref baseTypeParser));
            }
            else
            {
                // Set the base type for no metadata types, if we reach this point, and there isn't a parser, then we simply use the value from the template
                SetBaseType(ComputeTemplate().BaseType);
            }
        }

        /// <summary>
        /// This is used to set base type for generic types without metadata
        /// </summary>
        public void SetBaseType(DefType baseType)
        {
            Debug.Assert(_baseType == this || _baseType == baseType);
            _baseType = baseType;
        }

        protected override TypeFlags ComputeTypeFlags(TypeFlags mask)
        {
            TypeFlags flags = 0;

            if ((mask & TypeFlags.CategoryMask) != 0)
            {
                unsafe
                {
                    MethodTable* MethodTable = _genericTypeDefinition.ToEETypePtr();
                    EETypeElementType elementType = MethodTable->ElementType;
                    if (elementType == EETypeElementType.SystemArray)
                    {
                        // System.Array is a regular class in the type system
                        flags |= TypeFlags.Class;
                    }
                    else if (elementType <= EETypeElementType.Double &&
                        (MethodTable->IsGenericTypeDefinition || MethodTable->BaseType == typeof(System.Enum).TypeHandle.ToEETypePtr()))
                    {
                        // Enums are represented as their underlying type in the runtime type system
                        // Note: we check for IsGenericDefinition above to cover generic enums (base types are not set
                        // on generic definition MethodTable)
                        flags |= TypeFlags.Enum;
                    }
                    else
                    {
                        // Paranoid check that we handled enums above
                        Debug.Assert(MethodTable->IsGenericTypeDefinition ||
                            MethodTable->BaseType != typeof(System.Enum).TypeHandle.ToEETypePtr());

                        // The rest of values should be directly castable to TypeFlags
                        Debug.Assert((int)EETypeElementType.Void == (int)TypeFlags.Void);
                        Debug.Assert((int)EETypeElementType.Int32 == (int)TypeFlags.Int32);
                        Debug.Assert((int)EETypeElementType.IntPtr == (int)TypeFlags.IntPtr);
                        Debug.Assert((int)EETypeElementType.Double == (int)TypeFlags.Double);
                        Debug.Assert((int)EETypeElementType.Pointer == (int)TypeFlags.Pointer);
                        Debug.Assert((int)EETypeElementType.Class == (int)TypeFlags.Class);
                        Debug.Assert((int)EETypeElementType.Nullable == (int)TypeFlags.Nullable);

                        flags |= (TypeFlags)elementType;
                    }
                }
            }

            if ((mask & TypeFlags.AttributeCacheComputed) != 0)
            {
                flags |= TypeFlags.AttributeCacheComputed;

                unsafe
                {
                    MethodTable* MethodTable = _genericTypeDefinition.ToEETypePtr();
                    if (MethodTable->IsByRefLike)
                    {
                        flags |= TypeFlags.IsByRefLike;
                    }
                }
            }

            if ((mask & TypeFlags.HasGenericVarianceComputed) != 0)
            {
                flags |= TypeFlags.HasGenericVarianceComputed;

                unsafe
                {
                    if (_genericTypeDefinition.ToEETypePtr()->HasGenericVariance)
                        flags |= TypeFlags.HasGenericVariance;
                }
            }

            return flags;
        }

        // Canonicalization handling

        public override bool IsCanonicalSubtype(CanonicalFormKind policy)
        {
            foreach (TypeDesc t in Instantiation)
            {
                if (t.IsCanonicalSubtype(policy))
                {
                    return true;
                }
            }

            return false;
        }

        protected override TypeDesc ConvertToCanonFormImpl(CanonicalFormKind kind)
        {
            bool needsChange;
            Instantiation canonInstantiation = Context.ConvertInstantiationToCanonForm(Instantiation, kind, out needsChange);
            if (needsChange)
            {
                TypeDesc openType = GetTypeDefinition();
                return Context.ResolveGenericInstantiation((DefType)openType, canonInstantiation);
            }

            return this;
        }

        public override TypeDesc GetTypeDefinition()
        {
            if (_genericTypeDefinitionAsDefType != null)
                return _genericTypeDefinitionAsDefType;
            else
                return this;
        }

        public override TypeDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation)
        {
            TypeDesc[] clone = null;

            for (int i = 0; i < _instantiation.Length; i++)
            {
                TypeDesc uninst = _instantiation[i];
                TypeDesc inst = uninst.InstantiateSignature(typeInstantiation, methodInstantiation);
                if (inst != uninst)
                {
                    if (clone == null)
                    {
                        clone = new TypeDesc[_instantiation.Length];
                        for (int j = 0; j < clone.Length; j++)
                        {
                            clone[j] = _instantiation[j];
                        }
                    }
                    clone[i] = inst;
                }
            }

            return (clone == null) ? this : _genericTypeDefinitionAsDefType.Context.ResolveGenericInstantiation(_genericTypeDefinitionAsDefType, new Instantiation(clone));
        }

        public override Instantiation Instantiation
        {
            get
            {
                return _instantiation;
            }
        }

        public override TypeDesc UnderlyingType
        {
            get
            {
                if (!this.IsEnum)
                    return this;

                unsafe
                {
                    EETypeElementType elementType = RuntimeTypeHandle.ToEETypePtr()->ElementType;
                    Debug.Assert((int)EETypeElementType.Void == (int)WellKnownType.Void);
                    Debug.Assert((int)EETypeElementType.Int32 == (int)WellKnownType.Int32);
                    Debug.Assert((int)EETypeElementType.IntPtr == (int)WellKnownType.IntPtr);
                    Debug.Assert((int)EETypeElementType.Double == (int)WellKnownType.Double);
                    Debug.Assert(elementType <= EETypeElementType.Double);
                    return Context.GetWellKnownType((WellKnownType)elementType);
                }
            }
        }

        private void GetTypeNameHelper(out string name, out string nsName, out string assemblyName)
        {
            RuntimeTypeHandle genericDefinitionHandle = GetTypeDefinition().GetRuntimeTypeHandle();
            Debug.Assert(!genericDefinitionHandle.IsNull());

#if DEBUG
            QTypeDefinition qTypeDefinition;

            string enclosingDummy;

            // Try to get the name from metadata
            if (TypeLoaderEnvironment.TryGetMetadataForNamedType(genericDefinitionHandle, out qTypeDefinition))
            {
                TypeDefinitionHandle typeDefHandle = qTypeDefinition.NativeFormatHandle;
                typeDefHandle.GetFullName(qTypeDefinition.NativeFormatReader, out name, out enclosingDummy, out nsName);
                assemblyName = typeDefHandle.GetContainingModuleName(qTypeDefinition.NativeFormatReader);
            }
            else
#endif
            {
                name = genericDefinitionHandle.LowLevelToStringRawEETypeAddress();
                nsName = "";
                assemblyName = "?";
            }
        }

        public string NamespaceForDiagnostics
        {
            get
            {
                GetTypeNameHelper(out _, out string nsName, out _);
                return nsName;
            }
        }

        public string NameForDiagnostics
        {
            get
            {
                GetTypeNameHelper(out string name, out _, out _);
                return name;
            }
        }

        public string DiagnosticModuleName
        {
            get
            {
                GetTypeNameHelper(out _, out _, out string assemblyName);
                return assemblyName;
            }
        }

#if DEBUG
        private string _cachedToString;

        public override string ToString()
        {
            if (_cachedToString != null)
                return _cachedToString;

            StringBuilder sb = new StringBuilder();

            if (!_genericTypeDefinition.IsNull())
                sb.Append(_genericTypeDefinition.LowLevelToString());
            else if (!RuntimeTypeHandle.IsNull())
                sb.Append(RuntimeTypeHandle.LowLevelToString());

            if (!Instantiation.IsNull)
            {
                for (int i = 0; i < Instantiation.Length; i++)
                {
                    sb.Append(i == 0 ? "[" : ", ");
                    sb.Append(Instantiation[i].ToString());
                }
                if (Instantiation.Length > 0) sb.Append(']');
            }

            _cachedToString = sb.ToString();

            return _cachedToString;
        }
#endif
    }
}