From 809a2f53392633afccecd99045efe32904b87d3f Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Oct 2025 16:14:42 +0900 Subject: [PATCH 1/2] refactors using directives --- src/QuantitiesDotNet/GlobalUsings.cs | 5 +++-- src/QuantitiesDotNet/QuantityFormatInfo.cs | 12 ++++++------ src/QuantitiesDotNet/QuantityMetadata.cs | 2 -- src/QuantitiesDotNet/QuantityParseInfo.cs | 16 ++++++++-------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/QuantitiesDotNet/GlobalUsings.cs b/src/QuantitiesDotNet/GlobalUsings.cs index 4c5bc76..1dd798f 100644 --- a/src/QuantitiesDotNet/GlobalUsings.cs +++ b/src/QuantitiesDotNet/GlobalUsings.cs @@ -1,3 +1,4 @@ -#if NET7_0_OR_GREATER +#pragma warning disable IDE0005 +global using System; global using System.Numerics; -#endif +global using System.Text; diff --git a/src/QuantitiesDotNet/QuantityFormatInfo.cs b/src/QuantitiesDotNet/QuantityFormatInfo.cs index 0a57542..bd93fe3 100644 --- a/src/QuantitiesDotNet/QuantityFormatInfo.cs +++ b/src/QuantitiesDotNet/QuantityFormatInfo.cs @@ -10,14 +10,14 @@ public partial record QuantityFormatInfo( bool HasBrackets) { // lang=regex - private const string _EscapeMatcherPattern = @"\\(.)"; - private static readonly Regex _EscapeMatcher + private const string _escapeMatcherPattern = @"\\(.)"; + private static readonly Regex _escapeMatcher #if NET7_0_OR_GREATER = GenerateEscapeMatcher(); - [GeneratedRegex(_EscapeMatcherPattern)] + [GeneratedRegex(_escapeMatcherPattern)] private static partial Regex GenerateEscapeMatcher(); #else - = new(_EscapeMatcherPattern, RegexOptions.Compiled); + = new(_escapeMatcherPattern, RegexOptions.Compiled); #endif // lang=regex @@ -56,9 +56,9 @@ public static bool TryCompile( return false; } info = new( - _EscapeMatcher.Replace(match.Groups["number"].Value, "$1"), + _escapeMatcher.Replace(match.Groups["number"].Value, "$1"), match.Groups["spacing"].Value, - _EscapeMatcher.Replace(match.Groups["unit"].Value, "$1"), + _escapeMatcher.Replace(match.Groups["unit"].Value, "$1"), hasBrackets); return true; } diff --git a/src/QuantitiesDotNet/QuantityMetadata.cs b/src/QuantitiesDotNet/QuantityMetadata.cs index c06fff5..ca5c961 100644 --- a/src/QuantitiesDotNet/QuantityMetadata.cs +++ b/src/QuantitiesDotNet/QuantityMetadata.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace QuantitiesDotNet; diff --git a/src/QuantitiesDotNet/QuantityParseInfo.cs b/src/QuantitiesDotNet/QuantityParseInfo.cs index 2daa65e..ab96596 100644 --- a/src/QuantitiesDotNet/QuantityParseInfo.cs +++ b/src/QuantitiesDotNet/QuantityParseInfo.cs @@ -8,24 +8,24 @@ public partial record QuantityParseInfo( string UnitSelector) { // lang=regex - private const string _NumberPattern = @"(?[\+\-]?\d+(?:[\.\,]\d*)?(?:[Ee][\+\-]\d+)?)"; + private const string _numberPattern = @"(?[\+\-]?\d+(?:[\.\,]\d*)?(?:[Ee][\+\-]\d+)?)"; // lang=regex - private const string _BracketedUnitPattern = @"\[(?[\w/^*()\s\+\-]+)\]"; + private const string _bracketedUnitPattern = @"\[(?[\w/^*()\s\+\-]+)\]"; // lang=regex - private const string _BareUnitPattern = @"(?[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}_(][\w/^*()\s\+\-]*)"; + private const string _bareUnitPattern = @"(?[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}_(][\w/^*()\s\+\-]*)"; // lang=regex - private const string _FormatMatcherPattern = @$"^{_NumberPattern}\s*(?:{_BracketedUnitPattern}|{_BareUnitPattern})$"; + private const string _formatMatcherPattern = @$"^{_numberPattern}\s*(?:{_bracketedUnitPattern}|{_bareUnitPattern})$"; - private static readonly Regex _FormatMatcher + private static readonly Regex _formatMatcher #if NET7_0_OR_GREATER = GenerateFormatMatcher(); - [GeneratedRegex(_FormatMatcherPattern)] + [GeneratedRegex(_formatMatcherPattern)] private static partial Regex GenerateFormatMatcher(); #else - = new(_FormatMatcherPattern, RegexOptions.Compiled); + = new(_formatMatcherPattern, RegexOptions.Compiled); #endif public static bool TryCompile( @@ -33,7 +33,7 @@ public static bool TryCompile( [NotNullWhen(true)] out QuantityParseInfo? info) { expression = expression is { } ? expression.Trim() : ""; - var match = _FormatMatcher.Match(expression); + var match = _formatMatcher.Match(expression); if (!match.Success) { info = default!; From 3d3d2ec50d6c4226975d552ebff69320c895bd30 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Oct 2025 16:59:03 +0900 Subject: [PATCH 2/2] improves generator logic --- src/QuantitiesDotNet.Generators/Generator.cs | 57 ++++++---------- .../InternalHelpers.cs | 19 ++++++ .../QuantityDef.cs | 16 ----- .../QuantityDef_.cs | 57 ++++++++++++++++ .../QuantityImplementBuilder.cs | 66 +++++++++---------- ...itOperationDef.cs => QuantityOperation.cs} | 9 ++- .../{UnitSymbolDef.cs => UnitSymbol.cs} | 38 ++++++----- 7 files changed, 157 insertions(+), 105 deletions(-) create mode 100644 src/QuantitiesDotNet.Generators/InternalHelpers.cs delete mode 100644 src/QuantitiesDotNet.Generators/QuantityDef.cs create mode 100644 src/QuantitiesDotNet.Generators/QuantityDef_.cs rename src/QuantitiesDotNet.Generators/{UnitOperationDef.cs => QuantityOperation.cs} (84%) rename src/QuantitiesDotNet.Generators/{UnitSymbolDef.cs => UnitSymbol.cs} (66%) diff --git a/src/QuantitiesDotNet.Generators/Generator.cs b/src/QuantitiesDotNet.Generators/Generator.cs index d641cc3..896e796 100644 --- a/src/QuantitiesDotNet.Generators/Generator.cs +++ b/src/QuantitiesDotNet.Generators/Generator.cs @@ -1,4 +1,3 @@ -using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using SourceGeneratorToolkit; @@ -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(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 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 @@ -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); } -} \ No newline at end of file +} diff --git a/src/QuantitiesDotNet.Generators/InternalHelpers.cs b/src/QuantitiesDotNet.Generators/InternalHelpers.cs new file mode 100644 index 0000000..5c60935 --- /dev/null +++ b/src/QuantitiesDotNet.Generators/InternalHelpers.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace QuantitiesDotNet.Generators; + +internal static class InternalHelpers +{ + private static SymbolEqualityComparer Comparer => SymbolEqualityComparer.Default; + + public static IEnumerable GetAttributes( + this GeneratorAttributeSyntaxContext context, + string attributeFullName) + { + var attrType = context.SemanticModel.Compilation.GetTypeByMetadataName(attributeFullName); + return context + .TargetSymbol + .GetAttributes() + .Where(attr => Comparer.Equals(attr.AttributeClass, attrType)); + } +} \ No newline at end of file diff --git a/src/QuantitiesDotNet.Generators/QuantityDef.cs b/src/QuantitiesDotNet.Generators/QuantityDef.cs deleted file mode 100644 index cb1d56a..0000000 --- a/src/QuantitiesDotNet.Generators/QuantityDef.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace QuantitiesDotNet.Generators; - -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!); -} diff --git a/src/QuantitiesDotNet.Generators/QuantityDef_.cs b/src/QuantitiesDotNet.Generators/QuantityDef_.cs new file mode 100644 index 0000000..e65cd43 --- /dev/null +++ b/src/QuantitiesDotNet.Generators/QuantityDef_.cs @@ -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 UnitSymbols, + ImmutableArray 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!); +} \ No newline at end of file diff --git a/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs b/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs index a197afd..b6e715c 100644 --- a/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs +++ b/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs @@ -6,29 +6,25 @@ internal abstract class QuantityImplementBuilderBase( string typeNameBase, string tValue, bool isRefLikeType, - QuantityDef quantityDef, - IList unitSymbols, - IList unitOperations) + Dimension dimension, + IList unitSymbols, + IList unitOperations) { protected static readonly SourceStringHandler Empty = new(0, 0); - public static (QuantityImplementBuilderBase NonGeneric, QuantityImplementBuilderBase Generic) Create( - string typeNameBase, - bool isRefLikeType, - QuantityDef quantityDef, - IList unitSymbols, - IList 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); @@ -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 UnitSymbols => unitSymbols; - public IList UnitOperations => unitOperations; + public Dimension Dimension => dimension; + public IList UnitSymbols => unitSymbols; + public IList 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; } @@ -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); @@ -417,10 +413,10 @@ protected abstract(SourceStringHandler divisionOperato1If, SourceStringHandler m internal sealed class NonGenericQuantityImplementBuilder( string typeNameBase, bool isRefLikeType, - QuantityDef quantityDef, - IList unitSymbols, - IList unitOperations) - : QuantityImplementBuilderBase(typeNameBase, "double", isRefLikeType, quantityDef, unitSymbols, unitOperations) + Dimension dimension, + IList unitSymbols, + IList unitOperations) + : QuantityImplementBuilderBase(typeNameBase, "double", isRefLikeType, dimension, unitSymbols, unitOperations) { public override string TypeName => TypeNameBase; public override string DocTypeName => TypeNameBase; @@ -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}}); /// Gets quantity metadata instance for . public static QuantityMetadata Metadata => _Metadata; @@ -514,10 +510,10 @@ protected override (SourceStringHandler divisionOperato1If, SourceStringHandler internal sealed class GenericQuantityImplementBuilder( string typeNameBase, bool isRefLikeType, - QuantityDef quantityDef, - IList unitSymbols, - IList unitOperations) - : QuantityImplementBuilderBase(typeNameBase, "T", isRefLikeType, quantityDef, unitSymbols, unitOperations) + Dimension dimension, + IList unitSymbols, + IList unitOperations) + : QuantityImplementBuilderBase(typeNameBase, "T", isRefLikeType, dimension, unitSymbols, unitOperations) { public override string TypeName => $"{TypeNameBase}<{TValue}>"; public override string DocTypeName => TypeNameBase; diff --git a/src/QuantitiesDotNet.Generators/UnitOperationDef.cs b/src/QuantitiesDotNet.Generators/QuantityOperation.cs similarity index 84% rename from src/QuantitiesDotNet.Generators/UnitOperationDef.cs rename to src/QuantitiesDotNet.Generators/QuantityOperation.cs index 14763cc..921a5c4 100644 --- a/src/QuantitiesDotNet.Generators/UnitOperationDef.cs +++ b/src/QuantitiesDotNet.Generators/QuantityOperation.cs @@ -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) @@ -28,7 +29,7 @@ private static class QuantityOperationAttributeFields public const int ProductType = 2; } - public UnitOperationDef(AttributeData attr) + public QuantityOperation(AttributeData attr) : this( GetMultiplicantType(attr), GetMultiplierType(attr), @@ -36,6 +37,9 @@ public UnitOperationDef(AttributeData attr) { } + public static ImmutableArray GetOperations(IEnumerable enumerable) => + [.. enumerable.Select(attr => new QuantityOperation(attr))]; + private static string GetMultiplicantType(AttributeData attr) => (attr.ConstructorArguments[QuantityOperationAttributeFields.MultiplicantType].Value as INamedTypeSymbol) ?.Name @@ -50,4 +54,5 @@ private static string GetProductType(AttributeData attr) => (attr.ConstructorArguments[QuantityOperationAttributeFields.ProductType].Value as INamedTypeSymbol) ?.Name ?? throw new InvalidOperationException(); + } diff --git a/src/QuantitiesDotNet.Generators/UnitSymbolDef.cs b/src/QuantitiesDotNet.Generators/UnitSymbol.cs similarity index 66% rename from src/QuantitiesDotNet.Generators/UnitSymbolDef.cs rename to src/QuantitiesDotNet.Generators/UnitSymbol.cs index 6931b71..e6072af 100644 --- a/src/QuantitiesDotNet.Generators/UnitSymbolDef.cs +++ b/src/QuantitiesDotNet.Generators/UnitSymbol.cs @@ -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, @@ -49,24 +50,29 @@ private static class QuantityUnitAttributeFields }; #pragma warning restore format - public static IEnumerable GetUnitSymbols(AttributeData attr) + public static ImmutableArray GetUnitSymbols(IEnumerable 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 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)]; }