namespace G
{
[Generator(LanguageNames.CSharp)]
public class OperatorsGenerator : IIncrementalGenerator
{
private const string AttributeSource = @"//
using System;
namespace QWERTYkez.Mensura
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class OperatorsGeneratorAttribute : Attribute
{
public string CoeffName { get; }
public OperatorsGeneratorAttribute()
{
}
public OperatorsGeneratorAttribute(string coeffName)
{
CoeffName = coeffName;
}
}
}";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Внедряем атрибут
context.RegisterPostInitializationOutput(static postInitializationContext =>
{
postInitializationContext.AddSource(
".OperatorsGeneratorAttribute.g.cs",
SourceText.From(AttributeSource, Encoding.UTF8));
});
// 2. Ищем методы-операторы, помеченные атрибутом
IncrementalValuesProvider operatorDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: "QWERTYkez.Mensura.OperatorsGeneratorAttribute",
predicate: static (node, _) => node is OperatorDeclarationSyntax,
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
.Where(static m => m is not null)!;
// 3. Выполняем генерацию кода
context.RegisterSourceOutput(operatorDeclarations, static (spc, opInfo) => Execute(spc, opInfo));
}
private static OperatorInfo GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context)
{
var opDeclaration = (OperatorDeclarationSyntax)context.TargetNode;
if (opDeclaration.ParameterList.Parameters.Count < 2) return null;
if (context.SemanticModel.GetDeclaredSymbol(opDeclaration) is not IMethodSymbol symbol) return null;
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
var leftType = opDeclaration.ParameterList.Parameters[0].Type?.ToString() ?? "";
var rightType = opDeclaration.ParameterList.Parameters[1].Type?.ToString() ?? "";
var returnType = opDeclaration.ReturnType.ToString();
var isMultiplication = opDeclaration.OperatorToken.IsKind(SyntaxKind.AsteriskToken);
var isDivision = opDeclaration.OperatorToken.IsKind(SyntaxKind.SlashToken);
if (!isMultiplication && !isDivision) return null;
// АВТОМАТИЧЕСКИЙ ПОИСК: Ищем содержащий тип (структуру/рекорд) по синтаксическому дереву
string containingTypeName = leftType; // Фолбэк по умолчанию
var parentTypeDeclaration = opDeclaration.Ancestors()
.OfType()
.FirstOrDefault();
if (parentTypeDeclaration != null)
{
containingTypeName = parentTypeDeclaration.Identifier.ToString();
}
string coeffName = null;
string coeffType = containingTypeName; // По умолчанию считаем, что коэффициент в текущем классе
// Извлекаем аргумент атрибута
var attribute = opDeclaration.AttributeLists
.SelectMany(al => al.Attributes)
.FirstOrDefault(a => a.Name.ToString().EndsWith("OperatorsGenerator") || a.Name.ToString() == "OperatorsGenerator");
if (attribute?.ArgumentList != null && attribute.ArgumentList.Arguments.Count > 0)
{
var expr = attribute.ArgumentList.Arguments[0].Expression;
// Проверяем синтаксис nameof(...)
if (expr is InvocationExpressionSyntax invocation &&
invocation.Expression.ToString() == "nameof" &&
invocation.ArgumentList.Arguments.Count > 0)
{
var nameofArg = invocation.ArgumentList.Arguments[0].Expression;
// Если все-таки написали явно nameof(Type.Property)
if (nameofArg is MemberAccessExpressionSyntax memberAccess)
{
coeffType = memberAccess.Expression.ToString();
coeffName = memberAccess.Name.ToString();
}
else
{
coeffName = nameofArg.ToString();
// coeffType остается равным содержащему классу containingTypeName
}
}
else
{
coeffName = expr.ToString().Trim('"');
// coeffType остается равным содержащему классу containingTypeName
}
}
return new OperatorInfo
{
Namespace = namespaceName,
LeftType = leftType,
RightType = rightType,
ReturnType = returnType,
IsMultiplication = isMultiplication,
IsDivision = isDivision,
CoeffName = coeffName,
CoeffType = coeffType,
HasCoeff = !string.IsNullOrWhiteSpace(coeffName)
};
}
private static void Execute(SourceProductionContext context, OperatorInfo info)
{
var opType = info.IsMultiplication ? "Mul" : "Div";
var coeffSuffix = info.HasCoeff ? ".Coeff" : "";
var hintName = $"Op.{info.LeftType}.{opType}.{info.RightType}{coeffSuffix}.g.cs";
var source = info.IsMultiplication
? GenerateMultiplicationSource(info)
: GenerateDivisionSource(info);
context.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
}
private static string GenerateMultiplicationSource(OperatorInfo info)
{
return $@"//
using System;
using System.Collections.Generic;
namespace {info.Namespace};
public readonly partial record struct {info.LeftType}
{{
public static {info.ReturnType}[] operator *({info.LeftType} left, {info.RightType}[] right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
public static List<{info.ReturnType}> operator *({info.LeftType} left, List<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
public static IEnumerable<{info.ReturnType}> operator *({info.LeftType} left, IEnumerable<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
}}
public readonly partial record struct {info.RightType}
{{
public static {info.ReturnType}[] operator *({info.LeftType}[] left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
public static List<{info.ReturnType}> operator *(List<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
public static IEnumerable<{info.ReturnType}> operator *(IEnumerable<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
}}";
}
private static string GenerateDivisionSource(OperatorInfo info)
{
return $@"//
using System;
using System.Collections.Generic;
namespace {info.Namespace};
public readonly partial record struct {info.LeftType}
{{
public static {info.ReturnType}[] operator /({info.LeftType} left, {info.RightType}[] right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
public static List<{info.ReturnType}> operator /({info.LeftType} left, List<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
public static IEnumerable<{info.ReturnType}> operator /({info.LeftType} left, IEnumerable<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
}}
public readonly partial record struct {info.RightType}
{{
public static {info.ReturnType}[] operator /({info.LeftType}[] left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
public static List<{info.ReturnType}> operator /(List<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
public static IEnumerable<{info.ReturnType}> operator /(IEnumerable<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
}}";
}
private class OperatorInfo
{
public string Namespace { get; set; } = string.Empty;
public string LeftType { get; set; } = string.Empty;
public string RightType { get; set; } = string.Empty;
public string ReturnType { get; set; } = string.Empty;
public bool IsMultiplication { get; set; }
public bool IsDivision { get; set; }
public string CoeffName { get; set; }
public string CoeffType { get; set; }
public bool HasCoeff { get; set; } }
}
}