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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Runtime.TypeParsing;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using Mono.Cecil;
 
namespace Mono.Linker
{
	internal sealed class TypeNameResolver
	{
		readonly LinkContext _context;
 
		public readonly record struct TypeResolutionRecord (AssemblyDefinition ReferringAssembly, TypeDefinition ResolvedType);
 
		public TypeNameResolver (LinkContext context)
		{
			_context = context;
		}
 
		public bool TryResolveTypeName (
			string typeNameString,
			in DiagnosticContext diagnosticContext,
			[NotNullWhen (true)] out TypeReference? typeReference,
			[NotNullWhen (true)] out List<TypeResolutionRecord>? typeResolutionRecords,
			bool needsAssemblyName = true)
		{
			typeReference = null;
			typeResolutionRecords = null;
			if (string.IsNullOrEmpty (typeNameString))
				return false;
 
			TypeName? parsedTypeName;
			try {
				parsedTypeName = TypeParser.ParseTypeName (typeNameString);
			} catch (ArgumentException) {
				return false;
			} catch (System.IO.FileLoadException) {
				return false;
			}
 
			typeResolutionRecords = new List<TypeResolutionRecord> ();
			AssemblyDefinition? typeAssembly;
			if (parsedTypeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) {
				typeAssembly = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name);
				if (typeAssembly == null) {
					typeResolutionRecords = null;
					return false;
				}
 
				typeReference = ResolveTypeName (typeAssembly, assemblyQualifiedTypeName.TypeName, typeResolutionRecords);
				if (typeReference == null) {
					typeResolutionRecords = null;
				}
 
				return typeReference != null;
			}
 
			// If parsedTypeName doesn't have an assembly name in it but it does have a namespace,
			// search for the type in the calling object's assembly. If not found, look in the core
			// assembly.
			ICustomAttributeProvider? provider = diagnosticContext.Origin.Provider;
			typeAssembly = provider switch {
				AssemblyDefinition asm => asm,
				TypeDefinition type => type.Module?.Assembly,
				IMemberDefinition member => member.DeclaringType.Module.Assembly,
				null => null,
				_ => throw new NotSupportedException ()
			};
 
			if (typeAssembly != null && TryResolveTypeName (typeAssembly, parsedTypeName, out typeReference, typeResolutionRecords))
				return true;
 
			// If type is not found in the caller's assembly, try in core assembly.
			typeAssembly = _context.TryResolve (PlatformAssemblies.CoreLib);
			if (typeAssembly != null && TryResolveTypeName (typeAssembly, parsedTypeName, out typeReference, typeResolutionRecords))
				return true;
 
			// It is common to use Type.GetType for looking if a type is available.
			// If no type was found only warn and return null.
			if (needsAssemblyName && provider != null)
				diagnosticContext.AddDiagnostic (DiagnosticId.TypeWasNotFoundInAssemblyNorBaseLibrary, typeNameString);
 
			typeResolutionRecords = null;
			return false;
 
			bool TryResolveTypeName (AssemblyDefinition assemblyDefinition, TypeName? typeName, [NotNullWhen (true)] out TypeReference? typeReference, List<TypeResolutionRecord> typeResolutionRecords)
			{
				typeReference = null;
				if (assemblyDefinition == null)
					return false;
 
				typeReference = ResolveTypeName (assemblyDefinition, typeName, typeResolutionRecords);
				return typeReference != null;
			}
		}
 
		public bool TryResolveTypeName (
			AssemblyDefinition assembly,
			string typeNameString,
			[NotNullWhen (true)] out TypeReference? typeReference,
			[NotNullWhen (true)] out List<TypeResolutionRecord>? typeResolutionRecords)
		{
			typeResolutionRecords = new List<TypeResolutionRecord> ();
			typeReference = ResolveTypeName (assembly, TypeParser.ParseTypeName (typeNameString), typeResolutionRecords);
 
			if (typeReference == null)
				typeResolutionRecords = null;
 
			return typeReference != null;
		}
 
		TypeReference? ResolveTypeName (AssemblyDefinition assembly, TypeName? typeName, List<TypeResolutionRecord> typeResolutionRecords)
		{
			if (typeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) {
				// In this case we ignore the assembly parameter since the type name has assembly in it
				var assemblyFromName = _context.TryResolve (assemblyQualifiedTypeName.AssemblyName.Name);
				return assemblyFromName == null ? null : ResolveTypeName (assemblyFromName, assemblyQualifiedTypeName.TypeName, typeResolutionRecords);
			}
 
			if (assembly == null || typeName == null)
				return null;
 
			if (typeName is ConstructedGenericTypeName constructedGenericTypeName) {
				var genericTypeRef = ResolveTypeName (assembly, constructedGenericTypeName.GenericType, typeResolutionRecords);
				if (genericTypeRef == null)
					return null;
 
				Debug.Assert (genericTypeRef is TypeDefinition);
				var genericInstanceType = new GenericInstanceType (genericTypeRef);
				foreach (var arg in constructedGenericTypeName.GenericArguments) {
					var genericArgument = ResolveTypeName (assembly, arg, typeResolutionRecords);
					if (genericArgument == null)
						return null;
 
					genericInstanceType.GenericArguments.Add (genericArgument);
				}
 
				return genericInstanceType;
			} else if (typeName is HasElementTypeName elementTypeName) {
				var elementType = ResolveTypeName (assembly, elementTypeName.ElementTypeName, typeResolutionRecords);
				if (elementType == null)
					return null;
 
				return typeName switch {
					ArrayTypeName => new ArrayType (elementType),
					MultiDimArrayTypeName multiDimArrayTypeName => new ArrayType (elementType, multiDimArrayTypeName.Rank),
					ByRefTypeName => new ByReferenceType (elementType),
					PointerTypeName => new PointerType (elementType),
					_ => elementType
				};
			}
 
			TypeDefinition? resolvedType = assembly.MainModule.ResolveType (typeName.ToString (), _context);
 
			// 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;
		}
	}
}