Skip to content

Commit b23a3ff

Browse files
ci(v2.1): add dedicated net10+net11 preview pipeline; docs: union types design pass
- .github/workflows/ci-net11.yml: matrix over ubuntu/windows x net10.0/net11.0 for the preview branch (kept separate from master's ci.yml, installs both SDKs, runs the MTP test executable per framework). - docs/net11/UNION_TYPES_DESIGN.md: verified-against-RC1 blueprints for an opt-in SharpCoreDB.Unions namespace (union keyword + [Union]/IUnion), usage, backward-compat, perf notes, test strategy. - UPGRADE.md points to the design doc.
1 parent a8f11d8 commit b23a3ff

3 files changed

Lines changed: 214 additions & 1 deletion

File tree

‎.github/workflows/ci-net11.yml‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# CI for the v2.1 preview branch: builds and tests the multi-targeted (net10.0;net11.0)
2+
# core + tests on both target frameworks. Kept separate from ci.yml so the net10-only
3+
# master pipeline is never affected by the .NET 11 RC SDK pin in this branch's global.json.
4+
name: CI v2.1 Preview (.NET 11)
5+
6+
on:
7+
push:
8+
branches: [ release/v2.1.0.0-preview.1 ]
9+
pull_request:
10+
branches: [ release/v2.1.0.0-preview.1 ]
11+
12+
permissions:
13+
contents: read
14+
15+
env:
16+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
17+
DOTNET_CLI_TELEMETRY_OPTOUT: true
18+
DOTNET_NOLOGO: true
19+
CI_TEST_FILTER: 'Category!=Debug&Category!=Manual&Category!=Performance'
20+
21+
jobs:
22+
build-test:
23+
name: Build + Test (${{ matrix.framework }})
24+
runs-on: ${{ matrix.os }}
25+
timeout-minutes: 45
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
os: [ ubuntu-latest, windows-latest ]
30+
framework: [ net10.0, net11.0 ]
31+
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@v7
35+
with:
36+
fetch-depth: 0
37+
38+
# The branch's global.json pins the .NET 11 RC SDK, so install both SDKs. The RC SDK
39+
# builds the net10.0 target as well (SDKs are backwards compatible).
40+
- name: Setup .NET 10
41+
uses: actions/setup-dotnet@v6
42+
with:
43+
dotnet-version: '10.0.x'
44+
45+
- name: Setup .NET 11 (RC)
46+
uses: actions/setup-dotnet@v6
47+
with:
48+
dotnet-version: '11.0.100-rc.1'
49+
include-prerelease: true
50+
51+
- name: Display .NET info
52+
run: dotnet --info
53+
54+
- name: Restore dependencies
55+
run: dotnet restore tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj --configfile NuGet.Config
56+
57+
- name: Build (${{ matrix.framework }})
58+
run: dotnet build tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj --configuration Release --framework ${{ matrix.framework }} --no-restore
59+
60+
# MTP (xunit.v3) test hosts no longer support `dotnet test`'s VSTest flags; run the
61+
# MTP executable directly, which understands the same VSTest filter expression and writes TRX.
62+
- name: Test (${{ matrix.framework }})
63+
shell: bash
64+
run: |
65+
set -euo pipefail
66+
mkdir -p ./TestResults/SharpCoreDB.Tests/${{ matrix.framework }}
67+
tests/SharpCoreDB.Tests/bin/Release/${{ matrix.framework }}/SharpCoreDB.Tests \
68+
-filterVSTest "${{ env.CI_TEST_FILTER }}" \
69+
-result-trx "./TestResults/SharpCoreDB.Tests/${{ matrix.framework }}/SharpCoreDB.Tests.trx"
70+
timeout-minutes: 30
71+
env:
72+
CI: "true"
73+
GITHUB_ACTIONS: "true"

‎docs/net11/UNION_TYPES_DESIGN.md‎

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Union Types in SharpCoreDB — Design Pass (v2.1 Preview)
2+
3+
> Branch: `release/v2.1.0.0-preview.1` · Reference: [Union types — C# reference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union)
4+
5+
## Status
6+
7+
**Verified available** on .NET 11 RC1 (C# 15 preview): union types work via the `union` keyword and the
8+
`[System.Runtime.CompilerServices.Union]` attribute with `IUnion`. The runtime ships
9+
`UnionAttribute`/`IUnion` since .NET 11 Preview 5.
10+
11+
**Recommendation:** expose unions as a **new opt-in namespace** `SharpCoreDB.Unions` (net11.0-only),
12+
**never retrofit** existing result types (that would be breaking), and **hold off shipping** until the
13+
preview syntax stabilizes at GA. This document is the ready-to-implement blueprint.
14+
15+
## Verified syntax (compiles + runs on RC1)
16+
17+
```csharp
18+
public record class Cat(string Name);
19+
public record class Dog(string Name);
20+
21+
// Modern union keyword: the compiler generates a `struct : IUnion` with a `Value` property and
22+
// implicit conversions from each case type.
23+
public union Pet(Cat, Dog);
24+
25+
// Class-based union (reference semantics / private constructors / factories):
26+
[System.Runtime.CompilerServices.Union]
27+
public class Result<T> : System.Runtime.CompilerServices.IUnion
28+
{
29+
private readonly object? _value;
30+
public Result(T? value) { _value = value; }
31+
public object? Value => _value;
32+
}
33+
34+
// Exhaustive switch: removing any case below is a COMPILE ERROR.
35+
string Describe(Pet pet) => pet switch
36+
{
37+
Cat c => $"Cat {c.Name}",
38+
Dog d => $"Dog {d.Name}",
39+
};
40+
```
41+
42+
Key semantics: cases can be classes, structs, interfaces, type parameters, nullable types or other unions;
43+
value-type cases are boxed into `Value`; unions are structs without equality/cloning/deconstruction
44+
(put those on the case types, e.g. `record class` cases).
45+
46+
## Proposed API: `SharpCoreDB.Unions` (additive, opt-in, net11.0-only)
47+
48+
```csharp
49+
#if NET11_0_OR_GREATER
50+
namespace SharpCoreDB.Unions;
51+
52+
using System.Runtime.CompilerServices;
53+
using SharpCoreDB.DataStructures;
54+
55+
// Query outcome — compile-time exhaustive alternative to returning nullable/boolean-or-throw.
56+
public record class QuerySuccess(IReadOnlyList<Dictionary<string, object?>> Rows);
57+
public record class QueryNotFound;
58+
public record class QueryFailure(string Message);
59+
[Union]
60+
public partial class QueryOutcome : IUnion
61+
{
62+
private readonly object? _value;
63+
public QueryOutcome(QuerySuccess value) { _value = value; }
64+
public QueryOutcome(QueryNotFound value) { _value = value; }
65+
public QueryOutcome(QueryFailure value) { _value = value; }
66+
public object? Value => _value;
67+
}
68+
69+
// Execute outcome (rows affected or failure).
70+
public record class ExecuteSuccess(long RowsAffected);
71+
public record class ExecuteFailure(string Message);
72+
[Union]
73+
public partial class ExecuteOutcome : IUnion
74+
{
75+
private readonly object? _value;
76+
public ExecuteOutcome(ExecuteSuccess value) { _value = value; }
77+
public ExecuteOutcome(ExecuteFailure value) { _value = value; }
78+
public object? Value => _value;
79+
}
80+
81+
// Table lookup (used with the extension indexer db["name"]).
82+
public record class TableFound(TableInfo Table);
83+
public record class TableMissing(string Name);
84+
[Union]
85+
public partial class TableLookup : IUnion
86+
{
87+
private readonly object? _value;
88+
public TableLookup(TableFound value) { _value = value; }
89+
public TableLookup(TableMissing value) { _value = value; }
90+
public object? Value => _value;
91+
}
92+
#endif
93+
```
94+
95+
Usage (opt-in, only on net11.0):
96+
97+
```csharp
98+
using SharpCoreDB.Unions;
99+
100+
QueryOutcome outcome = ...; // returned by NEW opt-in methods only
101+
string text = outcome switch
102+
{
103+
QuerySuccess s => $"rows: {s.Rows.Count}",
104+
QueryNotFound => "not found",
105+
QueryFailure f => $"error: {f.Message}",
106+
};
107+
```
108+
109+
## Backward compatibility
110+
111+
- The file is source-gated `#if NET11_0_OR_GREATER` → **compiled only for net11.0**; net10.0 consumers
112+
never see it.
113+
- Existing `QueryResult` / `ExecuteResult` / `ExecuteSQL` signatures stay **byte-identical** on both TFMs.
114+
- Union values can be inspected generically via `IUnion`:
115+
`if (outcome is IUnion { Value: null }) { ... }`.
116+
117+
## Performance notes
118+
119+
- Value-type cases are boxed into the `object? Value`.
120+
- Unions are structs; for rich equality/deconstruction keep `record class` case types.
121+
- The `QueryOutcome` design above uses `[Union]` **class** unions (reference semantics) to keep the
122+
API familiar; the `union` keyword equivalent (`public union QueryOutcome(QuerySuccess, QueryNotFound, QueryFailure);`)
123+
is the lighter-weight struct alternative.
124+
125+
## Test strategy
126+
127+
1. **Compile-time exhaustiveness** (the core value): a test that switches over `QueryOutcome` with all
128+
cases; add a 4th case type later and watch CI fail until every switch handles it.
129+
2. **Runtime**: construct every case, switch-match it, verify `IUnion.Value` boxing, verify implicit
130+
conversion from each case type.
131+
3. Existing suite stays green: net10.0 1679 / net11.0 1688 (new tests are net11-gated).
132+
133+
## Risk & decision
134+
135+
- Union types are still **C# 15 preview**; the implementation (visibility, `Value` boxing, member
136+
requirements) can shift between RC and GA.
137+
- Because existing APIs are untouched and the new surface is opt-in, shipping early is low-risk, but
138+
frozen syntax is higher-value.
139+
- **Decision:** keep `SharpCoreDB.Unions` as a design blueprint for the 2.1 preview; implement it once
140+
.NET 11 GA / final C# 15 confirms the syntax.

‎docs/net11/UPGRADE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ List<string> names = Net11Additions.NewList<string>(128); // collection-expressi
4949

5050
| Item | Reason |
5151
|---|---|
52-
| **Union types** | Available in the C# 15 preview compiler, but the syntax is still stabilizing; will be designed as a new opt-in namespace (never retrofitted onto existing result types). |
52+
| **Union types** | Verified working in RC1 (`union` keyword + `[Union]`/`IUnion`); blueprint ready in `docs/net11/UNION_TYPES_DESIGN.md`. Held back from shipping until the preview syntax stabilizes at GA; will live in a new opt-in `SharpCoreDB.Unions` namespace (never retrofitted onto existing result types). |
5353
| **SIMD Zip / Unzip / Concat / CreateGeometricSequence** | Not applied yet — needs benchmarking against the existing `SimdHelper` AVX-512 → AVX2 → SSE/Neon kernels first. |
5454
| **Broad `[with(capacity: …)]` rewrites** | Would require `#if` gating across shared hot-path code; only exposed via the net11-only helper for now. |
5555

0 commit comments

Comments
 (0)