/*************************************************************************************************** Copyright (C) 2025 The Qt Company Ltd. SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ***************************************************************************************************/ using System.Collections; using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace Qt.DotNet.CodeGeneration { using Extensions; using Utils; using Utils.Collections.Concurrent; public class DependencyGraph : IReadOnlyDictionary> { public const string RootName = ""; private LazyFactory lazy = new(); private MetadataLoadContext loader; private DependencyGraph(MetadataLoadContext loader) { this.loader = loader; } public static async Task CreateAsync( MetadataLoadContext loader, Assembly source, IEnumerable excludedTypes) { Rules.SourceGraph = new DependencyGraph(loader); foreach (var excludedType in excludedTypes) Rules.SourceGraph.ExcludedTypes.Add(excludedType); if (!await Rules.SourceGraph.BuildAsync(source)) Rules.SourceGraph = null; } private ConcurrentDictionary> Nodes { get; } = new(); private ConcurrentDictionary> Edges { get; } = new(); public Type Root { get; private set; } public IEnumerable Connected(Type fromType) { return Edges.TryGetValue(fromType, out var toNodes) ? toNodes : Array.Empty(); } private Assembly GetAssembly(string name) => loader.LoadFromAssemblyName(name); private Assembly AdapterAssembly => lazy.Get(() => AdapterAssembly, () => GetAssembly("Qt.DotNet.Adapter")); public Type TypeOf(string name) => loader.CoreAssembly.GetType(name); public Type TypeOf(Type t) => TypeOf($"{t.FullName}, {t.Assembly.GetName().Name}"); public Type TypeOf() => TypeOf(typeof(T)); private Type TypeOfDelegate => lazy.Get(() => TypeOfDelegate, () => TypeOf()); private Type TypeOfTask => lazy.Get(() => TypeOfTask, () => TypeOf()); private Type TypeOfIEquatable => lazy.Get(() => TypeOfIEquatable, () => TypeOf("System.IEquatable`1")); private Type AttribAsync => lazy.Get(() => AttribAsync, () => TypeOf()); private Type AttribCompilerGenerated => lazy.Get(() => AttribCompilerGenerated, () => TypeOf()); private Type AttribExclude => lazy.Get(() => AttribExclude, () => TypeOf()); private Type AttribIgnore => lazy.Get(() => AttribIgnore, () => TypeOf()); public ConcurrentSet ExcludedTypes => lazy.Get(() => ExcludedTypes, () => new() { TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf("System.Collections.ObjectModel.ReadOnlyCollection`1"), TypeOf("System.Collections.Generic.IComparer`1"), TypeOf("System.Collections.Generic.IEqualityComparer`1"), TypeOf("System.Collections.Generic.IEnumerable`1"), TypeOf("System.Collections.Generic.IEnumerator`1"), TypeOf("System.Collections.Generic.List`1+Enumerator"), TypeOf("System.Collections.Generic.IList`1"), TypeOf("System.Collections.Generic.ICollection`1"), TypeOf("System.Collections.Generic.IDictionary`2"), TypeOf("System.Collections.Generic.Dictionary`2+ValueCollection"), TypeOf("System.Collections.Generic.Dictionary`2+KeyCollection"), TypeOf("System.Collections.Generic.Dictionary`2+Enumerator"), }); public ConcurrentSet ExcludedBaseTypes { get; } = new(); public ConcurrentSet BuiltInTypes => lazy.Get(() => BuiltInTypes, () => new() { TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(), TypeOf(typeof(void)) }); private bool IsConstructedTypeOfGenericType(Type type, Type genericType) { return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == genericType; } private bool IsBuiltIn(Type type) { if (type.QtAttributeData().Any()) return false; if (IsConstructedTypeOfGenericType(type, TypeOfIEquatable)) return true; if (BuiltInTypes.Contains(type)) return true; if (type.IsPrimitive) return true; if (type.Assembly == AdapterAssembly) return true; return false; } private bool IsSame(Type type, Type baseType) { if (type == baseType) return true; if (!type.IsConstructedGenericType || !baseType.IsGenericTypeDefinition) return false; return type.GetGenericTypeDefinition() == baseType; } private bool IsExcluded(Type type) { if (type.QtAttributeData().Any()) return false; if (type.IsGenericTypeDefinition || type.IsGenericParameter || type.IsGenericTypeParameter || type.IsGenericMethodParameter || type.ContainsGenericParameters) { return true; } if (type == TypeOfTask) return true; if (type.IsByRef || type.IsByRefLike || type.IsPointer) return true; if (ExcludedTypes.Any(x => IsSame(type, x))) return true; if (ExcludedBaseTypes.Any(x => type.IsDerivedFrom(x))) return true; if (ExcludedTypes.Any(x => type.IsNestedIn(x))) return true; if (ExcludedBaseTypes.Any(x => type.IsNestedIn(x))) return true; if (IsIgnored(type)) return true; if (type.IsAssignableTo(TypeOfDelegate)) return true; return false; } private bool IsIgnored(Type type) => IsIgnored(type?.GetCustomAttributesData()); private bool IsIgnored(MemberInfo info) => IsIgnored(info?.GetCustomAttributesData()); private bool IsIgnored(IEnumerable attribs) { if (attribs == null) return false; if (!attribs.Any(x => x.AttributeType == AttribIgnore)) return false; return true; } private bool IsValidMember(MemberInfo i) { if (i.QtAttributeData().Any()) return true; if (IsExcluded(i.ReflectedType)) return false; if (IsIgnored(i)) return false; if (i.DeclaringType?.Assembly == AdapterAssembly) return false; if (i.IsOverrideOf(AdapterAssembly)) return false; return true; } private async Task BuildAsync(Assembly assembly) { if ((Root = assembly.GetRootNode()) == null) return false; Nodes[Root] = new(); foreach (var attrib in assembly.GetCustomAttributesData()) { if (attrib.AttributeType != AttribExclude) continue; bool inherited = false; foreach (var namedArg in attrib.NamedArguments) { if (namedArg.MemberName != "Inherited") continue; if (namedArg.TypedValue.ArgumentType != TypeOf()) continue; if (namedArg.TypedValue.Value is not bool isInherited) continue; if (!isInherited) continue; inherited = true; break; } foreach (var arg in attrib.ConstructorArguments) { if (arg.Value is not IEnumerable ignoreTypes) continue; foreach (var ignoreTypeData in ignoreTypes) { var ignoreType = ignoreTypeData.Value switch { Type type => type, string name => TypeOf(name) ?? TypeOf(assembly.GetType(name)), _ => null }; if (ignoreType == null) continue; if (inherited) ExcludedBaseTypes.Add(ignoreType); else ExcludedTypes.Add(ignoreType); } } } var exportedTypes = assembly.ExportedTypes .Where(x => x.DeclaringType == null) .Append(TypeOf()); await Task.WhenAll(exportedTypes .Select(x => Task.Run(async () => await AddEdgeAsync(Root, x)))); if (!Edges.Any()) await AddEdgeAsync(Root, TypeOf()); return Nodes.Any(); } private async Task AddEdgeAsync(Type fromType, Type type) { if (fromType == type) return true; if (IsExcluded(type)) return false; if (IsBuiltIn(type)) return true; if (!Nodes.ContainsKey(type) && !await AddTypeAsync(type)) return false; Edges.TryAdd(fromType, new()); Edges[fromType].Add(type); return true; } private async Task AddTypeAsync(Type type) { if (IsBuiltIn(type)) return true; if (IsExcluded(type)) return false; var typeMembers = new ConcurrentSet(); if (!Nodes.TryAdd(type, typeMembers)) return true; if (type.IsAssignableTo(TypeOfDelegate)) { await Task.WhenAll(type.DelegateSignature() .Select(x => Task.Run(async () => await AddEdgeAsync(type, x)))); return true; } if (type.IsEnum) return true; var members = await Task.WhenAll( type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) .Where(x => IsValidMember(x)) .Select(x => Task.Run(async () => await (x switch { ConstructorInfo y => AddConstructorAsync(y), EventInfo y => AddEventAsync(y), FieldInfo y => AddFieldAsync(y), MethodInfo y => AddMethodAsync(y), PropertyInfo y => AddPropertyAsync(y), _ => Task.FromResult(false) }) ? x : null))); foreach (var member in members.Where(x => x != null)) typeMembers.Add(member); return true; } private bool HasAttributes(MemberInfo info, params Type[] attribs) { if (info.GetCustomAttributesData() is not { Count: > 0 } infoAttribs) return false; return infoAttribs.Any(x => attribs.Contains(x.AttributeType)); } private bool IsValidMethod(MethodBase info) { return !HasAttributes(info, AttribAsync, AttribCompilerGenerated) && !info.ContainsGenericParameters && !info.IsGenericMethod && !info.IsGenericMethodDefinition && (info.IsConstructor || !info.IsSpecialName); } private async Task AddConstructorAsync(ConstructorInfo info) { if (!IsValidMethod(info)) return false; var result = await Task.WhenAll( info.GetParameters() .Select(x => Task.Run(async () => await AddParameterAsync(x, info.ReflectedType)))); return !result.Contains(false); } private async Task AddMethodAsync(MethodInfo info, Type reflectedType = null) { reflectedType ??= info.ReflectedType; if (!IsValidMethod(info)) return false; var result = await Task.WhenAll( info.GetParameters() .Select(x => Task.Run(async () => await AddParameterAsync(x, reflectedType))) .Append(Task.Run(async () => await AddEdgeAsync(reflectedType, info.ReturnType)))); return !result.Contains(false); } private async Task AddParameterAsync(ParameterInfo info, Type reflectedType) { if (info.IsOut || info.ParameterType.IsByRef) return false; return await AddEdgeAsync(reflectedType, info.ParameterType); } private async Task AddEventAsync(EventInfo info) { var handler = info.EventHandlerType.GetMethod("Invoke"); if (handler == null) return false; return await AddMethodAsync(handler, info.ReflectedType); } private async Task AddPropertyAsync(PropertyInfo info) { return await AddEdgeAsync(info.ReflectedType, info.PropertyType); } private async Task AddFieldAsync(FieldInfo info) { if (info.IsSpecialName) return false; return await AddEdgeAsync(info.ReflectedType, info.FieldType); } public IEnumerable NodeSet() => Nodes .SelectMany(n => n.Value.Prepend(n.Key)).Distinct(); public IEnumerable NodeSet() => NodeSet().Where(n => n is T).Cast(); #region IReadOnlyDictionary> public bool ContainsKey(Type t) => ((IReadOnlyDictionary>)Nodes).ContainsKey(t); public bool TryGetValue(Type t, [MaybeNullWhen(false)] out ConcurrentSet mi) => ((IReadOnlyDictionary>)Nodes).TryGetValue(t, out mi); public IEnumerator>> GetEnumerator() => ((IEnumerable>>)Nodes).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Nodes).GetEnumerator(); public IEnumerable Keys => ((IReadOnlyDictionary>)Nodes).Keys; public IEnumerable> Values => ((IReadOnlyDictionary>)Nodes).Values; public int Count => ((IReadOnlyCollection>>)Nodes).Count; public ConcurrentSet this[Type key] => ((IReadOnlyDictionary>)Nodes)[key]; #endregion } public static class DependencyGraphNode { public static Type GetRootNode(this Assembly assembly) => assembly.GetType(DependencyGraph.RootName); public static bool IsRootNode(this MemberInfo node) => node.Name == DependencyGraph.RootName; } }