sharplib/lib/CodeGen.cs

174 lines
6.0 KiB
C#

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// S H A R P L I B
//
/// // (c) 2003..2025
using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.Collections.Immutable;
namespace ser;
public record CodeGenConfig : imm.Recorded<CodeGenConfig>
{
// Whitelists (if needed, otherwise rely on attributes/defaults)
public ImmutableDictionary<string, ImmutableList<string>> WLProps { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
public ImmutableDictionary<string, ImmutableList<string>> WLFields { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
// Default member types to process
public ser.Types TypesDefault { get; init; } = ser.Types.Fields | ser.Types.Props;
// How to handle backing fields (might be less relevant for code gen)
public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Regular;
public static CodeGenConfig Default { get; } = new CodeGenConfig();
}
public record GenMemberMeta(
MemberInfo Info,
Type Type,
string Name, // Name for code generation (usually original)
bool IsPrimitive,
bool IsCollection,
Type? CollectionElementType,
bool HasDo,
bool HasDont
);
public record TypeStructureInfo(
Type Type,
List<GenMemberMeta> Members,
bool IsValueType,
bool IsCollection
);
public class TypeStructureAnalyzer
{
private readonly ConcurrentDictionary<Type, TypeStructureInfo> _cache = new();
private readonly CodeGenConfig _cfg;
public TypeStructureAnalyzer(CodeGenConfig cfg) => _cfg = cfg;
public TypeStructureInfo Get(Type type) => _cache.GetOrAdd(type, BuildTypeInfo);
private TypeStructureInfo BuildTypeInfo(Type type)
{
var members = new List<GenMemberMeta>();
var typesTodo = type.GetCustomAttribute<ser.Ser>(true)?.Types ?? _cfg.TypesDefault;
bool doFields = typesTodo.HasFlag(ser.Types.Fields);
bool doProps = typesTodo.HasFlag(ser.Types.Props);
// Track processed names to avoid duplicates (e.g., field + prop)
var processedNames = new HashSet<string>();
// Process Properties First (often preferred interface)
if (doProps)
{
foreach (var pi in refl.GetAllProperties(type))
{
if (ProcessMember(pi, false, false, new HashSet<string>(), false, members))
{
processedNames.Add(pi.Name);
}
}
}
// Process Fields, avoiding those already covered by properties
if (doFields)
{
foreach (var fi in refl.GetAllFields(type))
{
var (isBacking, propName) = IsBackingField(fi);
string nameToTest = isBacking ? propName : fi.Name;
if (!processedNames.Contains(nameToTest))
{
if(ProcessMember(fi, false, false, new HashSet<string>(), false, members))
{
processedNames.Add(nameToTest);
}
}
}
}
return new TypeStructureInfo(
type,
members,
type.IsValueType,
typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string)
);
}
private bool ProcessMember(MemberInfo mi, bool filter, bool doImpls, HashSet<string> whitelist, bool isImm, List<GenMemberMeta> members)
{
var (hasDo, hasDont, propName) = GetMemberAttributes(mi, out var actualMiForAtts);
if (hasDont) return false;
if (mi.GetCustomAttribute<NonSerializedAttribute>(true) != null) return false;
if (mi.Name.Contains("k__BackingField") && !propName.Any()) return false; // Skip if backing but no prop found
string name = string.IsNullOrEmpty(propName) ? mi.Name : propName;
// Add filtering logic if needed (based on whitelist, etc.)
var type = (mi is FieldInfo fi) ? fi.FieldType : ((PropertyInfo)mi).PropertyType;
bool isCollection = typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
Type? elementType = isCollection ? GetElementType(type) : null;
bool isPrimitive = Type.GetTypeCode(type) != TypeCode.Object && !isCollection;
members.Add(new GenMemberMeta(
mi, type, name, isPrimitive, isCollection, elementType, hasDo, hasDont
));
return true;
}
private (bool, string) IsBackingField(FieldInfo fi)
{
if (fi.Name.StartsWith("<") && fi.Name.EndsWith("BackingField"))
{
var gtIndex = fi.Name.IndexOf('>');
if (gtIndex > 1) {
return (true, fi.Name.Substring(1, gtIndex - 1));
}
}
return (false, "");
}
private (bool hasDo, bool hasDont, string propName) GetMemberAttributes(MemberInfo mi, out MemberInfo actualMi)
{
actualMi = mi;
string propName = "";
if (mi is FieldInfo fi && IsBackingField(fi).Item1)
{
propName = IsBackingField(fi).Item2;
var propInfo = mi.DeclaringType?.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (propInfo != null) actualMi = propInfo;
} else if (mi is PropertyInfo) {
propName = mi.Name;
}
return (
actualMi.GetCustomAttribute<ser.Do>() != null,
actualMi.GetCustomAttribute<ser.Dont>() != null,
propName
);
}
private Type GetElementType(Type collectionType)
{
if (collectionType.IsArray) return collectionType.GetElementType()!;
if (collectionType.IsGenericType) return collectionType.GetGenericArguments().Last(); // Usually last (e.g., List<T>, Dict<K,V>)
return typeof(object); // Fallback
}
// Add GetFilters and FilterField if needed, or simplify as above
}