using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Robust.Serialization.Generator; internal static class Types { private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute"; private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute"; private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute"; private const string CopyByRefNamespace = "Robust.Shared.Serialization.Manager.Attributes.CopyByRefAttribute"; internal static bool IsPartial(TypeDeclarationSyntax type) { return type.Modifiers.IndexOf(SyntaxKind.PartialKeyword) != -1; } internal static bool IsDataDefinition(ITypeSymbol? type) { if (type == null) return false; return HasAttribute(type, DataDefinitionNamespace) || IsImplicitDataDefinition(type); } internal static bool IsDataField(ISymbol member, out ITypeSymbol type, out AttributeData attribute) { // TODO data records and other attributes if (member is IFieldSymbol field) { foreach (var attr in field.GetAttributes()) { if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace)) { type = field.Type; attribute = attr; return true; } } } else if (member is IPropertySymbol property) { foreach (var attr in property.GetAttributes()) { if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace)) { type = property.Type; attribute = attr; return true; } } } type = null!; attribute = null!; return false; } internal static bool IsImplicitDataDefinition(ITypeSymbol type) { if (HasAttribute(type, ImplicitDataDefinitionNamespace)) return true; foreach (var baseType in GetBaseTypes(type)) { if (HasAttribute(baseType, ImplicitDataDefinitionNamespace)) return true; } foreach (var @interface in type.AllInterfaces) { if (IsImplicitDataDefinitionInterface(@interface)) return true; } return false; } internal static bool IsImplicitDataDefinitionInterface(ITypeSymbol @interface) { if (HasAttribute(@interface, ImplicitDataDefinitionNamespace)) return true; foreach (var subInterface in @interface.AllInterfaces) { if (HasAttribute(subInterface, ImplicitDataDefinitionNamespace)) return true; } return false; } internal static IEnumerable GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all) { var interfaces = all ? type.AllInterfaces : type.Interfaces; foreach (var @interface in interfaces) { if (IsImplicitDataDefinitionInterface(@interface)) yield return @interface.ToDisplayString(); } } internal static bool IsNullableType(ITypeSymbol type) { if (type.NullableAnnotation == NullableAnnotation.Annotated) return true; if (type.OriginalDefinition.ToDisplayString() == "System.Nullable") return true; return false; } internal static bool IsNullableValueType(ITypeSymbol type) { return type.IsValueType && IsNullableType(type); } internal static bool IsMultidimensionalArray(ITypeSymbol type) { return type is IArrayTypeSymbol { Rank: > 1 }; } internal static bool CanBeCopiedByValue(ISymbol member, ITypeSymbol type) { if (type.OriginalDefinition.ToDisplayString() == "System.Nullable") return CanBeCopiedByValue(member, ((INamedTypeSymbol) type).TypeArguments[0]); if (type.TypeKind == TypeKind.Enum) return true; switch (type.SpecialType) { case SpecialType.System_Enum: case SpecialType.System_Boolean: case SpecialType.System_Char: case SpecialType.System_SByte: case SpecialType.System_Byte: case SpecialType.System_Int16: case SpecialType.System_UInt16: case SpecialType.System_Int32: case SpecialType.System_UInt32: case SpecialType.System_Int64: case SpecialType.System_UInt64: case SpecialType.System_Decimal: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_String: case SpecialType.System_DateTime: return true; } if (HasAttribute(member, CopyByRefNamespace)) return true; return false; } internal static string GetGenericTypeName(ITypeSymbol symbol) { var name = symbol.Name; if (symbol is INamedTypeSymbol { TypeParameters: { Length: > 0 } parameters }) { name += "<"; for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; name += parameter.Name; if (i < parameters.Length - 1) { name += ", "; } } name += ">"; } return name; } internal static string GetPartialTypeDefinitionLine(ITypeSymbol symbol) { var access = symbol.DeclaredAccessibility switch { Accessibility.Private => "private", Accessibility.ProtectedAndInternal => "protected internal", Accessibility.Protected => "protected", Accessibility.Internal => "internal", Accessibility.Public => "public", _ => "public" }; var typeKeyword = "partial "; if (symbol.TypeKind == TypeKind.Interface) { typeKeyword += "interface"; } else { if (symbol.IsRecord) { typeKeyword += symbol.IsValueType ? "record struct" : "record"; } else { typeKeyword += symbol.IsValueType ? "struct" : "class"; } if (symbol.IsAbstract) { typeKeyword = $"abstract {typeKeyword}"; } } var typeName = GetGenericTypeName(symbol); return $"{access} {typeKeyword} {typeName}"; } internal static bool Inherits(ITypeSymbol type, string parent) { foreach (var baseType in GetBaseTypes(type)) { if (baseType.ToDisplayString() == parent) return true; } return false; } internal static bool ImplementsInterface(ITypeSymbol type, string interfaceName) { foreach (var interfaceType in type.AllInterfaces) { if (interfaceType.ToDisplayString().Contains(interfaceName)) { return true; } } return false; } internal static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member) { if (member is IFieldSymbol field) { return field.IsReadOnly; } else if (member is IPropertySymbol property) { if (property.SetMethod == null) return true; if (property.SetMethod.IsInitOnly) return type.IsReferenceType; return false; } return false; } internal static bool NeedsEmptyConstructor(ITypeSymbol type) { if (type is not INamedTypeSymbol named) return false; if (named.InstanceConstructors.Length == 0) return true; foreach (var constructor in named.InstanceConstructors) { if (constructor.Parameters.Length == 0 && !constructor.IsImplicitlyDeclared) { return false; } } return true; } internal static bool IsVirtualClass(ITypeSymbol type) { return type.IsReferenceType && !type.IsSealed && type.TypeKind != TypeKind.Interface; } internal static bool HasAttribute(ISymbol symbol, string attributeName) { foreach (var attribute in symbol.GetAttributes()) { if (attribute.AttributeClass?.ToDisplayString() == attributeName) return true; } return false; } internal static bool TryGetAttribute(ISymbol symbol, string attributeName, [NotNullWhen(true)] out AttributeData? data) { foreach (var attribute in symbol.GetAttributes()) { if (attribute.AttributeClass?.ToDisplayString() == attributeName) { data = attribute; return true; } } data = null; return false; } internal static IEnumerable GetBaseTypes(ITypeSymbol type) { var baseType = type.BaseType; while (baseType != null) { yield return baseType; baseType = baseType.BaseType; } } }