Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
34c3805
add requirements.md
Oct 25, 2025
c14b9d7
add requirements
Oct 25, 2025
b3e61b2
update requirements.md
Oct 25, 2025
3fcc9a5
update requirements.md
Oct 26, 2025
eecb8c6
update test lib, add test app
Oct 26, 2025
c6af13c
migrate to slnx
Oct 26, 2025
218d7e0
wip
Oct 29, 2025
9efa8a7
add lyra
Oct 29, 2025
13ff8b4
update README.md
Oct 29, 2025
bd722cc
Merge branch 'refs/heads/lyra' into unions2
Oct 29, 2025
54efcc1
update lyra version
Oct 29, 2025
18acb18
add polyfills generator
Dec 7, 2025
1cd62df
implement janus
Dec 9, 2025
f1acb48
implement lyra
Dec 9, 2025
22dea92
fixed nuget setup
Dec 9, 2025
b9aeab4
wip
Dec 9, 2025
5960017
remove unions generator
Dec 9, 2025
deba3da
increment ikvm msbuild version
Dec 9, 2025
02bc097
add solution name
Dec 9, 2025
2299568
remove globals.json
Dec 9, 2025
065233f
project changes
Dec 9, 2025
27318a5
add Equa
Dec 9, 2025
6675627
utilize janus
Dec 9, 2025
f52ac7c
utilize lyra
Dec 9, 2025
d085ba0
fix all warnings
Dec 20, 2025
3f33de3
Revert "fix all warnings"
Dec 20, 2025
87a39d4
add utilitygenerators.dev
Dec 21, 2025
6f80712
emit janus package on debug build to dev
Dec 21, 2025
ba3b0ac
use janus directly
Dec 21, 2025
f751f58
fix dsl generator
Dec 21, 2025
49d85fa
allow user provided equality operators
Dec 21, 2025
4d1a761
Merge branch 'release' into unions2
SleepWellPupper Dec 21, 2025
386cf83
update README.md
Dec 21, 2025
9b887c0
throw generic ArgumentException if validation does not
Dec 21, 2025
8d8805e
add named factory methods for overload ambiguity resolution
Dec 21, 2025
b31ece3
make generic type parameter check netstandard2 compliant
Dec 21, 2025
4ba1aec
improve space formatting
Dec 21, 2025
2f4a187
Merge remote-tracking branch 'origin/unions2' into unions2
Dec 21, 2025
e9546e9
Merge branch 'release' into unions2
Dec 21, 2025
ad09438
update README.md
Dec 21, 2025
f95d413
remove potentially incorrect break statement
Dec 28, 2025
01d2356
support if/else variant switch for small variant counts
Dec 28, 2025
f99b79f
support 1d array variants
Dec 28, 2025
4b7704e
add array tests
Dec 28, 2025
e6ac318
add benchmarks
Jan 3, 2026
1fed84c
allow ref struct state in switch
Jan 4, 2026
d3d57cf
Merge branch 'release' into unions2
Jan 4, 2026
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
4 changes: 0 additions & 4 deletions CSharpSourceBuilder.TestApplication/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
// See https://aka.ms/new-console-template for more information

using RhoMicro.CodeAnalysis;
using RhoMicro.CodeAnalysis.Lyra;

using var builder = new CSharpSourceBuilder();


7 changes: 1 addition & 6 deletions Janus.Analyzers/Components/JsonConverterComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,7 @@ public override void Write(
{
ct.ThrowIfCancellationRequested();

b.Append(
$$"""
global::System.Text.Json.JsonSerializer.Serialize(writer, value.CastTo{{v.Name}}, options);
break;
"""
);
b.Append($"global::System.Text.Json.JsonSerializer.Serialize(writer, value.CastTo{v.Name}, options);");
}, static (_, b, ct) =>
{
ct.ThrowIfCancellationRequested();
Expand Down
15 changes: 15 additions & 0 deletions Janus.Analyzers/Components/SwitchComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public void Switch<TState>(

b.Append($"{typeof(Action)}<{v.Type.NullableName}, TState> on{v.Name}");
}, separator: ",\n")}})
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
{{new VariantsSwitchComponent(m, static (v, _, b, ct) =>
{
Expand Down Expand Up @@ -141,6 +144,9 @@ public void Switch<TState>(

b.Append($"{typeof(Action)}<{v.Type.NullableName}, TState>? on{v.Name} = null");
}, separator: ",\n")}})
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
{{new VariantsSwitchComponent(m, static (v, _, b, ct) =>
{
Expand Down Expand Up @@ -214,6 +220,9 @@ public TResult Switch<TState, TResult>(

b.Append($"{func}<{v.Type.NullableName}, TState, TResult> on{v.Name}");
}, separator: ",\n")}})
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
{{new VariantsSwitchComponent(m, static (v, _, b, ct) =>
{
Expand Down Expand Up @@ -287,6 +296,9 @@ public TResult Switch<TState, TResult>(

b.Append($"{func}<{v.Type.NullableName}, TState, TResult>? on{v.Name} = null");
}, separator: ",\n")}})
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
{{new VariantsSwitchComponent(m, static (v, _, b, ct) =>
{
Expand Down Expand Up @@ -370,6 +382,9 @@ public TResult Switch<TState, TResult>(

b.Append($"{func}<{v.Type.NullableName}, TState, TResult>? on{v.Name} = null");
}, separator: ",\n")}})
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
{{new VariantsSwitchComponent(m, static (v, _, b, ct) =>
{
Expand Down
44 changes: 44 additions & 0 deletions Janus.Analyzers/Components/VariantsSwitchComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,50 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation
{
cancellationToken.ThrowIfCancellationRequested();

if (Model.Variants.Count < 24)
{
AppendIfElseStatements(builder, cancellationToken);
}
else
{
AppendSwitchStatement(builder, cancellationToken);
}
}

private void AppendIfElseStatements(CSharpSourceBuilder builder, CancellationToken cancellationToken)
{
foreach (var variant in Model.Variants)
{
builder.AppendLine($"if({VariantExpression} is VariantKind.{variant.Name})")
.AppendLine('{')
.Indent();

Append.Invoke(variant, Model, builder, cancellationToken);

builder.AppendLine()
.Detent()
.Append("} else ");
}

builder.AppendLine('{')
.Indent();

if (Default is not null)
{
Default.Invoke(Model, builder, cancellationToken);
}
else
{
builder.Append("throw CreateUnknownVariantException();");
}

builder.AppendLine()
.Detent()
.AppendLine('}');
}

private void AppendSwitchStatement(CSharpSourceBuilder builder, CancellationToken cancellationToken)
{
builder.AppendLine($"switch({VariantExpression})")
.AppendLine('{')
.Indent();
Expand Down
47 changes: 36 additions & 11 deletions Janus.Analyzers/Models/UnionTypeAttribute.Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ partial class UnionTypeAttribute
[StructLayout(LayoutKind.Auto)]
partial record struct Model
{
internal static readonly SymbolDisplayFormat TypeDisplayFormat = new SymbolDisplayFormat(
internal static readonly SymbolDisplayFormat TypeDisplayFormat = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
Expand Down Expand Up @@ -64,10 +64,21 @@ private void Initialize(ITypeParameterSymbol target, CancellationToken ct)
[InitializationMethod(StateTypeName = "TypeArgumentState")]
private void Initialize(ITypeSymbol variant, CancellationToken ct)
{
var isInterface = variant.TypeKind is TypeKind.Interface;
var docsId = variant.GetDocumentationCommentId() ?? String.Empty;
var isArray = false;

if (variant is INamedTypeSymbol
var extractedVariant = variant;

if (variant is IArrayTypeSymbol { ElementType: { } elementType })
{
isArray = true;
extractedVariant = elementType;
}

var isInterface = extractedVariant.TypeKind is TypeKind.Interface;
var docsId = extractedVariant.GetDocumentationCommentId() ?? String.Empty;
var variantName = Name;

if (extractedVariant is INamedTypeSymbol
{
OriginalDefinition:
{
Expand All @@ -76,29 +87,37 @@ private void Initialize(ITypeSymbol variant, CancellationToken ct)
TypeArguments: [{ } actualVariant]
})
{
Name ??= actualVariant.Name;
variantName ??= actualVariant.Name;
var name = actualVariant.ToDisplayString(TypeDisplayFormat);
Type = new(
actualVariant.IsUnmanagedType ? VariantTypeKind.Unmanaged : VariantTypeKind.Value,
isArray
? VariantTypeKind.Reference
: actualVariant.IsUnmanagedType
? VariantTypeKind.Unmanaged
: VariantTypeKind.Value,
IsNullable: true,
IsInterface: isInterface,
Name: name,
DocsId: docsId);
}
else
{
Name ??= variant.Name;
variantName ??= extractedVariant.Name;
var name = variant.ToDisplayString(TypeDisplayFormat);
Type = variant switch
Type = extractedVariant switch
{
{ IsUnmanagedType: true } =>
new(VariantTypeKind.Unmanaged,
new(isArray
? VariantTypeKind.Reference
: VariantTypeKind.Unmanaged,
IsNullable: false,
IsInterface: isInterface,
Name: name,
DocsId: docsId),
{ IsValueType: true } =>
new(VariantTypeKind.Value,
new(isArray
? VariantTypeKind.Reference
: VariantTypeKind.Value,
IsNullable: false,
IsInterface: isInterface,
Name: name,
Expand All @@ -110,13 +129,19 @@ private void Initialize(ITypeSymbol variant, CancellationToken ct)
Name: name,
DocsId: docsId),
_ =>
new(VariantTypeKind.Unknown,
new(isArray
? VariantTypeKind.Reference
: VariantTypeKind.Unknown,
IsNullable: false,
IsInterface: false,
Name: name,
DocsId: docsId),
};
}

Name ??= isArray
? $"{variantName}Array"
: variantName;
}

public VariantTypeModel Type { get; private set; }
Expand Down
5 changes: 5 additions & 0 deletions Janus.Benchmarks/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
</Project>
28 changes: 28 additions & 0 deletions Janus.Benchmarks/Janus.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Janus.Benchmarks</AssemblyName>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8"/>
<PackageReference Include="Dunet" Version="1.11.3" />
<PackageReference Include="RhoMicro.BdnLogging" Version="1.0.3" />
<PackageReference Include="OneOf" Version="3.0.271"/>
<PackageReference Include="OneOf.Extended" Version="3.0.271" />
<ProjectReference Include="..\Janus.Analyzers\Janus.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Janus.CodeFixes\Janus.CodeFixes.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Janus.Library\Janus.Library.csproj"/>
</ItemGroup>

<ItemGroup>
<Folder Include="BenchmarkDotNet.Artifacts\" />
</ItemGroup>

</Project>
60 changes: 60 additions & 0 deletions Janus.Benchmarks/MemoryOverlayingBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MPL-2.0

using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using OneOf;
using RhoMicro.CodeAnalysis;

// Dunet doesn't emit the nested type as part of the hint name, and
// so Roslyn doesn't emit its generated source when conflicting hint
// names are used.
// This is why the Dunet types are not modeled as nested types.
[Dunet.Union]
public partial record MemoryOverlayingBenchmarkDunetUnion
{
public partial record Int32(System.Int32 Value);

public partial record Single(System.Single Value);

public partial record Double(System.Double Value);

public partial record Int64(System.Int64 Value);
}

[SimpleJob, MemoryDiagnoser]
public partial class MemoryOverlayingBenchmark
{
public class ClassOneOfUnion : OneOfBase<Int32, Single, Double, Int64>
{
public ClassOneOfUnion(OneOf<Int32, Single, Double, Int64> input) : base(input)
{
}
}

[UnionType<Int32, Single, Double, Int64>]
public sealed partial class ClassJanusUnion;

[UnionType<Int32, Single, Double, Int64>]
public readonly partial struct StructJanusUnion;

[Benchmark]
public StructJanusUnion StructJanusUnionAllocations() => new(GetValue());

[Benchmark(Baseline = true)]
public ClassJanusUnion ClassJanusUnionAllocations() => new(GetValue());

[Benchmark]
public ClassOneOfUnion ClassOneOfUnionAllocations() => new(GetValue());

[Benchmark]
public OneOf<Int32, Single, Double, Int64> StructOneOfUnionAllocations()
=> GetValue(); // OneOf does not publish a ctor

[Benchmark]
public MemoryOverlayingBenchmarkDunetUnion DunetUnionAllocations()
=> new MemoryOverlayingBenchmarkDunetUnion.Int32(GetValue());

[MethodImpl(MethodImplOptions.NoInlining)]
private static Int32 GetValue() => 0;
}
Loading
Loading