File: Linker\TypeNameResolver.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Mono.Cecil;
 
using TypeName = System.Reflection.Metadata.TypeName;
using TypeNameParseOptions = System.Reflection.Metadata.TypeNameParseOptions;
using AssemblyNameInfo = System.Reflection.Metadata.AssemblyNameInfo;
using TypeNameHelpers = System.Reflection.Metadata.TypeNameHelpers;
 
#nullable enable
 
namespace Mono.Linker
{
	internal sealed partial class TypeNameResolver
	{
		readonly ITryResolveMetadata _metadataResolver;
		readonly ITryResolveAssemblyName _assemblyResolver;
 
		private static readonly TypeNameParseOptions s_typeNameParseOptions = new () { MaxNodes = int.MaxValue };
 
		public readonly record struct TypeResolutionRecord (AssemblyDefinition ReferringAssembly, TypeDefinition ResolvedType);
 
		public TypeNameResolver (ITryResolveMetadata metadataResolver, ITryResolveAssemblyName assemblyNameResolver)
		{
			_metadataResolver = metadataResolver;
			_assemblyResolver = assemblyNameResolver;
		}
 
		public bool TryResolveTypeName (
			AssemblyDefinition assembly,
			string typeNameString,
			[NotNullWhen (true)] out TypeReference? typeReference,
			[NotNullWhen (true)] out List<TypeResolutionRecord>? typeResolutionRecords)
		{
			typeResolutionRecords = new List<TypeResolutionRecord> ();
			if (!TypeName.TryParse (typeNameString, out TypeName? parsedTypeName, s_typeNameParseOptions)) {
				typeReference = null;
				return false;
			}
			typeReference = ResolveTypeName (assembly, parsedTypeName, typeResolutionRecords);
 
			if (typeReference == null)
				typeResolutionRecords = null;
 
			return typeReference != null;
		}
 
		TypeReference? ResolveTypeName (AssemblyDefinition originalAssembly, TypeName? typeName, List<TypeResolutionRecord> typeResolutionRecords)
		{
			if (typeName == null)
				return null;
 
			AssemblyDefinition? assembly = originalAssembly;
			if (typeName.AssemblyName is AssemblyNameInfo assemblyName)
				// In this case we ignore the assembly parameter since the type name has assembly in it
				assembly = _assemblyResolver.TryResolve (assemblyName.Name);
 
			if (assembly == null)
				return null;
 
			if (typeName.IsConstructedGenericType) {
				var genericTypeRef = ResolveTypeName (assembly, typeName.GetGenericTypeDefinition (), typeResolutionRecords);
				if (genericTypeRef == null)
					return null;
 
				Debug.Assert (genericTypeRef is TypeDefinition);
				var genericInstanceType = new GenericInstanceType (genericTypeRef);
				foreach (var arg in typeName.GetGenericArguments()) {
					var genericArgument = ResolveTypeName (assembly, arg, typeResolutionRecords);
					if (genericArgument == null)
						return null;
 
					genericInstanceType.GenericArguments.Add (genericArgument);
				}
 
				return genericInstanceType;
			} else if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef) {
				var elementType = ResolveTypeName (assembly, typeName.GetElementType (), typeResolutionRecords);
				if (elementType == null)
					return null;
 
				if (typeName.IsArray)
					return typeName.IsSZArray ? new ArrayType (elementType) : new ArrayType (elementType, typeName.GetArrayRank ());
				if (typeName.IsByRef)
					return new ByReferenceType (elementType);
				if (typeName.IsPointer)
					return new PointerType (elementType);
				Debug.Fail("Unreachable");
				return null;
			}
 
			Debug.Assert (typeName.IsSimple);
			TypeName topLevelTypeName = typeName;
			while (topLevelTypeName.IsNested)
				topLevelTypeName = topLevelTypeName.DeclaringType!;
			Debug.Assert (topLevelTypeName.AssemblyName == typeName.AssemblyName);
			TypeDefinition? resolvedType = GetSimpleTypeFromModule (typeName, assembly.MainModule);
 
			// True type references (like generics and arrays) don't count as actually resolved types, they're just wrappers
			// so only record type resolutions for types which are actually resolved.
			if (resolvedType != null) {
				typeResolutionRecords.Add (new (assembly, resolvedType));
				return resolvedType;
			}
 
			// If it didn't resolve and wasn't assembly-qualified, we also try core library
			var coreLibrary = _metadataResolver.TryResolve (originalAssembly.MainModule.TypeSystem.Object)?.Module.Assembly;
			if (coreLibrary is null)
				return null;
 
			if (topLevelTypeName.AssemblyName == null && assembly != coreLibrary) {
				resolvedType = GetSimpleTypeFromModule (typeName, coreLibrary.MainModule);
				if (resolvedType != null) {
					typeResolutionRecords.Add (new (coreLibrary, resolvedType));
					return resolvedType;
				}
			}
 
			return null;
 
			TypeDefinition? GetSimpleTypeFromModule (TypeName typeName, ModuleDefinition module)
			{
				if (typeName.IsNested)
				{
					TypeDefinition? type = GetSimpleTypeFromModule (typeName.DeclaringType!, module);
					if (type == null)
						return null;
					return GetNestedType (type, TypeNameHelpers.Unescape (typeName.Name));
				}
 
				return module.ResolveType (TypeNameHelpers.Unescape (typeName.FullName), _metadataResolver);
			}
 
			TypeDefinition? GetNestedType (TypeDefinition type, string nestedTypeName)
			{
				if (!type.HasNestedTypes)
					return null;
 
				foreach (var nestedType in type.NestedTypes) {
					if (nestedType.Name == nestedTypeName)
						return nestedType;
				}
 
				return null;
			}
		}
	}
}