Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 21 additions & 36 deletions src/QuantitiesDotNet.Generators/Generator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SourceGeneratorToolkit;
Expand All @@ -8,51 +7,37 @@ namespace QuantitiesDotNet.Generators;
[Generator(LanguageNames.CSharp)]
public partial class Generator : IIncrementalGenerator
{
private const string _attrName = "QuantitiesDotNet.QuantityAttribute";
private const string _unitAttrName = "QuantitiesDotNet.QuantityUnitAttribute";
private const string _operationAttrName = "QuantitiesDotNet.QuantityOperationAttribute";

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var quantityAttributeSymbol = context
.CompilationProvider
.GetMetadata("QuantitiesDotNet.QuantityAttribute");
var quantityUnitAttributeSymbol = context
.CompilationProvider
.GetMetadata("QuantitiesDotNet.QuantityUnitAttribute");
var quantityOperationAttributeSymbol = context
.CompilationProvider
.GetMetadata("QuantitiesDotNet.QuantityOperationAttribute");
var attributedFiles = context.SyntaxProvider
.FindAttributedMembers<StructDeclarationSyntax, INamedTypeSymbol>(quantityAttributeSymbol);

var source = attributedFiles
.Combine(quantityAttributeSymbol
.Combine(quantityUnitAttributeSymbol
.Combine(quantityOperationAttributeSymbol)));
var source = context.SyntaxProvider.ForAttributeWithMetadataName(
_attrName,
static (node, _) => node is StructDeclarationSyntax,
static (cxt, _) =>
{
var symbol = (INamedTypeSymbol)cxt.TargetSymbol;
var typeName = symbol.Name;
var isRefLike = symbol.IsRefLikeType;
var dimension = Dimension.GetDimension(cxt.Attributes[0]);
var unitSymbols = UnitSymbol.GetUnitSymbols(cxt.GetAttributes(_unitAttrName));
var operations = QuantityOperation.GetOperations(cxt.GetAttributes(_operationAttrName));
return new QuantityDef(typeName, isRefLike, dimension, unitSymbols, operations);
});
context.RegisterSourceOutput(source, GenerateUnitTypeImplements);
}


private void GenerateUnitTypeImplements(
SourceProductionContext context,
(AttributedMemberInfo<INamedTypeSymbol> info,
(INamedTypeSymbol qAttr, (INamedTypeSymbol unitAttr, INamedTypeSymbol opAttr))) tpl)
QuantityDef quantityDef)
{
var canceller = context.CancellationToken;
canceller.ThrowIfCancellationRequested();

var (info, (qAttr, (qUnitAttr, qOpAttr))) = tpl;
var attributes = info.TargetSymbol.GetAttributes();
var qDef = attributes
.Single(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, qAttr));
var unitDefs = attributes
.Where(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, qUnitAttr));
var operationDefs = attributes
.Where(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, qOpAttr));

var (nonGeneric, generic) = QuantityImplementBuilderBase.Create(
info.TargetSymbol.Name,
info.TargetSymbol.IsRefLikeType,
QuantityDef.GetQuantityDef(qDef),
[.. unitDefs.SelectMany(UnitSymbolDef.GetUnitSymbols)],
[.. operationDefs.Select(static attr => new UnitOperationDef(attr))]);
var (nonGeneric, generic) = QuantityImplementBuilderBase.Create(quantityDef);
var sb = new SourceBuilderSlim();
sb.AppendLine("""
#nullable enable
Expand Down Expand Up @@ -86,7 +71,7 @@ namespace QuantitiesDotNet.Generic
sb.AppendLine("#endif");
var source = sb.Build();
context.AddSource(
$"{info.TargetSymbol.Name}.g.cs",
$"{quantityDef.TypeName}.g.cs",
source);
}
}
}
19 changes: 19 additions & 0 deletions src/QuantitiesDotNet.Generators/InternalHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.CodeAnalysis;

namespace QuantitiesDotNet.Generators;

internal static class InternalHelpers
{
private static SymbolEqualityComparer Comparer => SymbolEqualityComparer.Default;

public static IEnumerable<AttributeData> GetAttributes(
this GeneratorAttributeSyntaxContext context,
string attributeFullName)
{
var attrType = context.SemanticModel.Compilation.GetTypeByMetadataName(attributeFullName);
return context
.TargetSymbol
.GetAttributes()
.Where(attr => Comparer.Equals(attr.AttributeClass, attrType));
}
}
16 changes: 0 additions & 16 deletions src/QuantitiesDotNet.Generators/QuantityDef.cs

This file was deleted.

57 changes: 57 additions & 0 deletions src/QuantitiesDotNet.Generators/QuantityDef_.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace QuantitiesDotNet.Generators;

public sealed record QuantityDef(
string TypeName,
bool IsRefLike,
Dimension Dimension,
ImmutableArray<UnitSymbol> UnitSymbols,
ImmutableArray<QuantityOperation> Equations)
{
public override int GetHashCode() => TypeName.GetHashCode();

public bool Equals(QuantityDef? other) =>
Equals(this, other);

public static bool Equals(QuantityDef? x, QuantityDef? y) =>
(x, y) switch
{
(null, null) => true,
(null, _) => false,
(_, null) => false,
_ => x.TypeName == y.TypeName
&& x.IsRefLike == y.IsRefLike
&& x.Dimension.Equals(y.Dimension)
&& x.UnitSymbols.SequenceEqual(y.UnitSymbols)
&& x.Equations.SequenceEqual(y.Equations),
};
}

public record QuantityDef_(int L, int M, int T, int I, int Th, int N, int J)
{
public static QuantityDef_ GetQuantityDef(AttributeData attr)
=> new(
(int)attr.ConstructorArguments[0].Value!,
(int)attr.ConstructorArguments[1].Value!,
(int)attr.ConstructorArguments[2].Value!,
(int)attr.ConstructorArguments[3].Value!,
(int)attr.ConstructorArguments[4].Value!,
(int)attr.ConstructorArguments[5].Value!,
(int)attr.ConstructorArguments[6].Value!);
}


public record Dimension(int L, int M, int T, int I, int Th, int N, int J)
{
public static Dimension GetDimension(AttributeData attr)
=> new(
(int)attr.ConstructorArguments[0].Value!,
(int)attr.ConstructorArguments[1].Value!,
(int)attr.ConstructorArguments[2].Value!,
(int)attr.ConstructorArguments[3].Value!,
(int)attr.ConstructorArguments[4].Value!,
(int)attr.ConstructorArguments[5].Value!,
(int)attr.ConstructorArguments[6].Value!);
}
66 changes: 31 additions & 35 deletions src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,25 @@ internal abstract class QuantityImplementBuilderBase(
string typeNameBase,
string tValue,
bool isRefLikeType,
QuantityDef quantityDef,
IList<UnitSymbolDef> unitSymbols,
IList<UnitOperationDef> unitOperations)
Dimension dimension,
IList<UnitSymbol> unitSymbols,
IList<QuantityOperation> unitOperations)
{
protected static readonly SourceStringHandler Empty = new(0, 0);

public static (QuantityImplementBuilderBase NonGeneric, QuantityImplementBuilderBase Generic) Create(
string typeNameBase,
bool isRefLikeType,
QuantityDef quantityDef,
IList<UnitSymbolDef> unitSymbols,
IList<UnitOperationDef> unitOperations)
public static (QuantityImplementBuilderBase NonGeneric, QuantityImplementBuilderBase Generic) Create(QuantityDef quantityDef)
{
var (typeNameBase, isRefLike, dimension, unitSymbols, unitOperations) = quantityDef;
var nonGeneric = new NonGenericQuantityImplementBuilder(
typeNameBase,
isRefLikeType,
quantityDef,
isRefLike,
dimension,
unitSymbols,
unitOperations);
var generic = new GenericQuantityImplementBuilder(
typeNameBase,
isRefLikeType,
quantityDef,
isRefLike,
dimension,
unitSymbols,
unitOperations);
return (nonGeneric, generic);
Expand All @@ -37,13 +33,13 @@ public static (QuantityImplementBuilderBase NonGeneric, QuantityImplementBuilder
public string TypeNameBase => typeNameBase;
public string TValue => tValue;
public bool IsRefLikeType => isRefLikeType;
public QuantityDef QuantityDef => quantityDef;
public IList<UnitSymbolDef> UnitSymbols => unitSymbols;
public IList<UnitOperationDef> UnitOperations => unitOperations;
public Dimension Dimension => dimension;
public IList<UnitSymbol> UnitSymbols => unitSymbols;
public IList<QuantityOperation> UnitOperations => unitOperations;

public UnitSymbolDef PrimaryUnit => _primaryUnit ??= GetPrimaryUnit();
private UnitSymbolDef? _primaryUnit;
private UnitSymbolDef GetPrimaryUnit() => UnitSymbols.FirstOrDefault() ?? new UnitSymbolDef("RawValue", "", 1, false);
public UnitSymbol PrimaryUnit => _primaryUnit ??= GetPrimaryUnit();
private UnitSymbol? _primaryUnit;
private UnitSymbol GetPrimaryUnit() => UnitSymbols.FirstOrDefault() ?? new UnitSymbol("RawValue", "", 1, false);

public abstract string TypeName { get; }
public abstract string DocTypeName { get; }
Expand Down Expand Up @@ -341,7 +337,7 @@ protected virtual void GenerateExternalOperatorPre(SourceBuilderSlim sb)
{
}

private void GenerateExternalOperator(SourceBuilderSlim sb, UnitOperationDef op)
private void GenerateExternalOperator(SourceBuilderSlim sb, QuantityOperation op)
{
var product = GetRelativeType(op.ProductType);
var multiplicant = GetRelativeType(op.MultiplicantType);
Expand Down Expand Up @@ -417,10 +413,10 @@ protected abstract(SourceStringHandler divisionOperato1If, SourceStringHandler m
internal sealed class NonGenericQuantityImplementBuilder(
string typeNameBase,
bool isRefLikeType,
QuantityDef quantityDef,
IList<UnitSymbolDef> unitSymbols,
IList<UnitOperationDef> unitOperations)
: QuantityImplementBuilderBase(typeNameBase, "double", isRefLikeType, quantityDef, unitSymbols, unitOperations)
Dimension dimension,
IList<UnitSymbol> unitSymbols,
IList<QuantityOperation> unitOperations)
: QuantityImplementBuilderBase(typeNameBase, "double", isRefLikeType, dimension, unitSymbols, unitOperations)
{
public override string TypeName => TypeNameBase;
public override string DocTypeName => TypeNameBase;
Expand Down Expand Up @@ -452,13 +448,13 @@ protected override void GenerateMetadata(SourceBuilderSlim sb)
// for reflection of ref struct, explicitly named backing field is provided.
internal static readonly QuantityMetadata _Metadata = new(
"{{TypeNameBase.Substring(1)}}",
L : {{QuantityDef.L}},
M : {{QuantityDef.M}},
T : {{QuantityDef.T}},
I : {{QuantityDef.I}},
Th: {{QuantityDef.Th}},
N : {{QuantityDef.N}},
J : {{QuantityDef.J}});
L : {{Dimension.L}},
M : {{Dimension.M}},
T : {{Dimension.T}},
I : {{Dimension.I}},
Th: {{Dimension.Th}},
N : {{Dimension.N}},
J : {{Dimension.J}});

/// <summary> Gets quantity metadata instance for <see cref="{{DocTypeName}}" />. </summary>
public static QuantityMetadata Metadata => _Metadata;
Expand Down Expand Up @@ -514,10 +510,10 @@ protected override (SourceStringHandler divisionOperato1If, SourceStringHandler
internal sealed class GenericQuantityImplementBuilder(
string typeNameBase,
bool isRefLikeType,
QuantityDef quantityDef,
IList<UnitSymbolDef> unitSymbols,
IList<UnitOperationDef> unitOperations)
: QuantityImplementBuilderBase(typeNameBase, "T", isRefLikeType, quantityDef, unitSymbols, unitOperations)
Dimension dimension,
IList<UnitSymbol> unitSymbols,
IList<QuantityOperation> unitOperations)
: QuantityImplementBuilderBase(typeNameBase, "T", isRefLikeType, dimension, unitSymbols, unitOperations)
{
public override string TypeName => $"{TypeNameBase}<{TValue}>";
public override string DocTypeName => TypeNameBase;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Collections.Immutable;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;

namespace QuantitiesDotNet.Generators;

public record UnitOperationDef(
public record QuantityOperation(
string MultiplicantType,
string MultiplierType,
string ProductType)
Expand All @@ -28,14 +29,17 @@ private static class QuantityOperationAttributeFields
public const int ProductType = 2;
}

public UnitOperationDef(AttributeData attr)
public QuantityOperation(AttributeData attr)
: this(
GetMultiplicantType(attr),
GetMultiplierType(attr),
GetProductType(attr))
{
}

public static ImmutableArray<QuantityOperation> GetOperations(IEnumerable<AttributeData> enumerable) =>
[.. enumerable.Select(attr => new QuantityOperation(attr))];

private static string GetMultiplicantType(AttributeData attr)
=> (attr.ConstructorArguments[QuantityOperationAttributeFields.MultiplicantType].Value as INamedTypeSymbol)
?.Name
Expand All @@ -50,4 +54,5 @@ private static string GetProductType(AttributeData attr)
=> (attr.ConstructorArguments[QuantityOperationAttributeFields.ProductType].Value as INamedTypeSymbol)
?.Name
?? throw new InvalidOperationException();

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace QuantitiesDotNet.Generators;

public record UnitSymbolDef(
public record UnitSymbol(
string MajorName,
string ShortName,
double Scale,
Expand Down Expand Up @@ -49,24 +50,29 @@ private static class QuantityUnitAttributeFields
};
#pragma warning restore format

public static IEnumerable<UnitSymbolDef> GetUnitSymbols(AttributeData attr)
public static ImmutableArray<UnitSymbol> GetUnitSymbols(IEnumerable<AttributeData> attrs)
{
var majorName = GetMajorName(attr);
var shortName = GetShortName(attr);
var scale = GetScale(attr);
var exportsSymbol = GetExportsShorthandSymbol(attr);
yield return new(majorName, shortName, scale, exportsSymbol);

var prefix = attr.ConstructorArguments[QuantityUnitAttributeFields.Prefix].Value is int flag ? flag : 0;
var powerOfPrefix = attr.ConstructorArguments[QuantityUnitAttributeFields.PowerOfPrefix].Value is int pop ? pop : 1;
var prefixSet = _UnitPrefix.Where(tpl => (tpl.flag & prefix) != 0);
var camelMajorName = char.ToLower(majorName[0]) + majorName.Substring(1);
foreach (var (_, name, symbol, pScale) in prefixSet)
static IEnumerable<UnitSymbol> core(AttributeData attr)
{
var exMajorName = name + camelMajorName;
var exShortName = symbol + shortName;
yield return new(exMajorName, exShortName, scale * Math.Pow(pScale, powerOfPrefix), exportsSymbol);
var majorName = GetMajorName(attr);
var shortName = GetShortName(attr);
var scale = GetScale(attr);
var exportsSymbol = GetExportsShorthandSymbol(attr);
yield return new(majorName, shortName, scale, exportsSymbol);

var prefix = attr.ConstructorArguments[QuantityUnitAttributeFields.Prefix].Value is int flag ? flag : 0;
var powerOfPrefix = attr.ConstructorArguments[QuantityUnitAttributeFields.PowerOfPrefix].Value is int pop ? pop : 1;
var prefixSet = _UnitPrefix.Where(tpl => (tpl.flag & prefix) != 0);
var camelMajorName = char.ToLower(majorName[0]) + majorName.Substring(1);
foreach (var (_, name, symbol, pScale) in prefixSet)
{
var exMajorName = name + camelMajorName;
var exShortName = symbol + shortName;
yield return new(exMajorName, exShortName, scale * Math.Pow(pScale, powerOfPrefix), exportsSymbol);
}

}
return [.. attrs.SelectMany(core)];
}


Expand Down
Loading