using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Immutable; using System.Linq; using System.Text; namespace G; [Generator(LanguageNames.CSharp)] public class CollectionsOperatorsGenerator : IIncrementalGenerator { private const string AttributeShortName = "CollectionsOperatorsGenerator"; private const string AttributeFullName = AttributeShortName + "Attribute"; private const string AttributeSource = @"namespace QWERTYkez.Mensura; [System.AttributeUsage(System.AttributeTargets.Struct | System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class CollectionsOperatorsGeneratorAttribute : System.Attribute { }"; // Какие типы коллекций поддерживаем private static readonly (string Type, string Selector)[] CollectionTypes = [ ("T[]", "array => array.Select(u => left * u).ToArray()"), // но нужно адаптировать под конкретный тип // Проще генерировать отдельные методы для каждого типа ]; public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(ctx => ctx.AddSource($"{AttributeFullName}.g", SourceText.From(AttributeSource, Encoding.UTF8))); var operatorsPipeline = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (node, _) => IsTargetType(node), transform: static (ctx, _) => GetTypeInfo(ctx)) .Where(info => info.HasValue) // использование nullable value type .Select((info, _) => info!.Value) .Collect(); context.RegisterSourceOutput(operatorsPipeline, GenerateOperators); } private static bool IsTargetType(SyntaxNode node) { if (node is not TypeDeclarationSyntax typeDecl) return false; // Должен быть partial и не generic if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) return false; if (typeDecl.TypeParameterList != null) return false; // Проверяем наличие атрибута на самом типе foreach (var attrList in typeDecl.AttributeLists) foreach (var attr in attrList.Attributes) { string name = attr.Name.ToString(); if (name == AttributeShortName || name == AttributeFullName) return true; } // Или на операторах внутри foreach (var member in typeDecl.Members) { if (member is OperatorDeclarationSyntax opDecl && opDecl.AttributeLists.SelectMany(al => al.Attributes) .Any(a => a.Name.ToString().Contains(AttributeShortName))) return true; } return false; } private static TypeInfoData? GetTypeInfo(GeneratorSyntaxContext ctx) { var typeDecl = (TypeDeclarationSyntax)ctx.Node; var semanticModel = ctx.SemanticModel; var symbol = semanticModel.GetDeclaredSymbol(typeDecl); if (symbol == null) return null; string namespaceName = symbol.ContainingNamespace?.ToString() ?? ""; // Формируем заголовок (для отладочных целей) var headerBuilder = new StringBuilder(); foreach (var modifier in typeDecl.Modifiers) headerBuilder.Append(modifier.Text).Append(' '); if (typeDecl is RecordDeclarationSyntax) headerBuilder.Append("record "); headerBuilder.Append(typeDecl.Identifier.Text); string header = headerBuilder.ToString(); // Собираем операторы var operators = ImmutableArray.CreateBuilder(); foreach (var member in typeDecl.Members) { if (member is not OperatorDeclarationSyntax opDecl) continue; if (opDecl.ParameterList.Parameters.Count != 2) continue; if (!opDecl.AttributeLists.SelectMany(al => al.Attributes) .Any(a => a.Name.ToString().Contains(AttributeShortName))) continue; operators.Add(new Operation( opDecl.OperatorToken.Text, opDecl.ParameterList.Parameters[0].Type!.ToString(), opDecl.ParameterList.Parameters[1].Type!.ToString(), opDecl.ReturnType.ToString() )); } if (operators.Count == 0) return null; return new TypeInfoData(namespaceName, typeDecl.Identifier.Text, header, operators.ToImmutable()); } private static void GenerateOperators(SourceProductionContext context, ImmutableArray types) { foreach (var group in types.GroupBy(t => t.Namespace)) { var document = new StringBuilder(); document.AppendLine("namespace ").Append(group.Key).AppendLine(";"); document.AppendLine("using System.Collections.Generic;"); document.AppendLine("using System.Linq;"); var allOperators = group.SelectMany(t => t.Operators).ToList(); var multiplications = allOperators.Where(op => op.OperatorToken == "*").ToList(); var divisions = allOperators.Where(op => op.OperatorToken == "/").ToList(); // Генерация для умножения: left * collection и collection * left foreach (var mul in multiplications) { // left * collection document.AppendLine($@" public static {mul.ReturnType}[] operator *({mul.TypeA} left, {mul.TypeB}[] right) => right.Select(u => left * u).ToArray(); public static List<{mul.ReturnType}> operator *({mul.TypeA} left, List<{mul.TypeB}> right) => right.Select(u => left * u).ToList(); public static IEnumerable<{mul.ReturnType}> operator *({mul.TypeA} left, IEnumerable<{mul.TypeB}> right) => right.Select(u => left * u); "); // collection * left document.AppendLine($@" public static {mul.ReturnType}[] operator *({mul.TypeB}[] left, {mul.TypeA} right) => left.Select(u => u * right).ToArray(); public static List<{mul.ReturnType}> operator *(List<{mul.TypeB}> left, {mul.TypeA} right) => left.Select(u => u * right).ToList(); public static IEnumerable<{mul.ReturnType}> operator *(IEnumerable<{mul.TypeB}> left, {mul.TypeA} right) => left.Select(u => u * right); "); } // Деление: left / collection (left - тип A, collection - тип B) foreach (var div in divisions.Where(op => op.TypeA != null)) // все деления { // left / collection document.AppendLine($@" public static {div.ReturnType}[] operator /({div.TypeA} left, {div.TypeB}[] right) => right.Select(u => left / u).ToArray(); public static List<{div.ReturnType}> operator /({div.TypeA} left, List<{div.TypeB}> right) => right.Select(u => left / u).ToList(); public static IEnumerable<{div.ReturnType}> operator /({div.TypeA} left, IEnumerable<{div.TypeB}> right) => right.Select(u => left / u); "); // collection / right (где right - тип B) document.AppendLine($@" public static {div.ReturnType}[] operator /({div.TypeA}[] left, {div.TypeB} right) => left.Select(u => u / right).ToArray(); public static List<{div.ReturnType}> operator /(List<{div.TypeA}> left, {div.TypeB} right) => left.Select(u => u / right).ToList(); public static IEnumerable<{div.ReturnType}> operator /(IEnumerable<{div.TypeA}> left, {div.TypeB} right) => left.Select(u => u / right); "); } context.AddSource($"operations.{group.Key}.g", document.ToString()); } } private readonly struct TypeInfoData(string ns, string name, string header, ImmutableArray ops) { public string Namespace { get; } = ns; public string TypeName { get; } = name; public string Header { get; } = header; public ImmutableArray Operators { get; } = ops; } private readonly struct Operation(string token, string a, string b, string ret) { public string OperatorToken { get; } = token; public string TypeA { get; } = a; public string TypeB { get; } = b; public string ReturnType { get; } = ret; } }