diff --git a/src/Sharpy.Cli.Tests/E2E/HostAssemblyVisibilityE2ETests.cs b/src/Sharpy.Cli.Tests/E2E/HostAssemblyVisibilityE2ETests.cs
index 4573a72e6..3721e13d8 100644
--- a/src/Sharpy.Cli.Tests/E2E/HostAssemblyVisibilityE2ETests.cs
+++ b/src/Sharpy.Cli.Tests/E2E/HostAssemblyVisibilityE2ETests.cs
@@ -18,7 +18,11 @@ namespace Sharpy.Cli.Tests.E2E;
/// SPY0300 at base 486c9a88a and compiled, then died with FileNotFoundException, at effba7776;
/// from system.command_line import RootCommand compiled then died (exit 134,
/// FileNotFoundException System.CommandLine) at both. A refusal (SPY0301, the system.* prefix's
-/// answer for a namespace that exists nowhere) is the fix by direction. The
+/// answer for a namespace that exists nowhere) is the fix by direction. An UNUSED import of a host
+/// package (from system.composition import ExportAttribute, never referenced) compiled with
+/// SPY0452 at base and is SPY0301 now: worked → rejected, under the owner's 2026-10-15 ruling on the
+/// same mechanism (the unused system.reactive.linq cell on #2250) — the base answer depended on
+/// which packages the host happened to ship, which is the contract's violation. The
/// -r cell is the positive control: the same import with the package given as a reference
/// compiles and runs.
///
@@ -34,6 +38,11 @@ public class HostAssemblyVisibilityE2ETests : IDisposable
"from system.command_line import RootCommand\n\n"
+ "def main() -> None:\n r = RootCommand(\"hello\")\n print(r.description)\n";
+ /// An unused host-package import: the owner-ruled worked → rejected direction (see remarks).
+ private const string CompositionUnusedProgram =
+ "from system.composition import ExportAttribute\n\n"
+ + "def main() -> None:\n print(\"ok\")\n";
+
private readonly TempWorkspace _ws = new();
public void Dispose() => _ws.Dispose();
@@ -46,6 +55,7 @@ public class HostAssemblyVisibilityE2ETests : IDisposable
[Theory]
[InlineData("extensions_primitives", PrimitivesProgram, "SPY0300")]
[InlineData("command_line", CommandLineProgram, "SPY0301")]
+ [InlineData("composition_unused", CompositionUnusedProgram, "SPY0301")]
public void HostPackageImport_WithoutReference_IsRefusedAtImport(string cell, string program, string refusal)
{
_ws.WriteSpy(program);
diff --git a/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationCorpus.cs b/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationCorpus.cs
index 551c8c936..d92e20c73 100644
--- a/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationCorpus.cs
+++ b/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationCorpus.cs
@@ -1,11 +1,27 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Sharpy.Compiler.Tests.Fuzz;
using Sharpy.TestInfrastructure.Integration;
+using Sharpy.TestInfrastructure.Registry;
namespace Sharpy.Compiler.Tests.Conformance;
/// One program of the in-process isolation corpus: a stable id (the allowlist key), the
/// file name the compile names it by, and its source.
-internal sealed record IsolationProgram(string Id, string FileName, string Source);
+internal sealed record IsolationProgram(string Id, string FileName, string Source)
+{
+ /// Assembly paths this program is compiled against, on top of the instance's own (Sharpy.Core).
+ public IReadOnlyList References { get; init; } = Array.Empty();
+
+ ///
+ /// A positive control on the solo oracle, or null: (the fresh answer
+ /// must be a success) or a diagnostic code the fresh answer must carry — the probe reads what it
+ /// is about, so a divergence from it is a difference in the thing the seam decides.
+ ///
+ public string? OracleMustCarry { get; init; }
+
+ internal const string OracleSucceeds = "success";
+}
/// The two operations the sweep drives on every instance kind.
internal enum IsolationOperation
@@ -20,6 +36,20 @@ internal enum IsolationOperation
/// One step of a thread's schedule.
internal readonly record struct IsolationStep(IsolationProgram Program, IsolationOperation Operation);
+///
+/// The programs one sweep run schedules: the literal roster (every program under both operations),
+/// the builtin-shadowing triggers (analyze only — the write the seam refuses happens in analysis),
+/// the typed probes that read what a write would change (both operations, and once more after the
+/// last trigger), and the referenced-library pair of this run (both operations, first in every
+/// schedule so an instance that caches one analysis context per reference set switches contexts
+/// before the trigger → probe body, never inside it).
+///
+internal sealed record IsolationRoster(
+ IReadOnlyList Named,
+ IReadOnlyList LibraryPair,
+ IReadOnlyList Triggers,
+ IReadOnlyList Probes);
+
///
/// The corpus and the schedule generator of (P54, #2250).
///
@@ -39,15 +69,35 @@ internal enum IsolationOperation
/// imported namespace (the attribute resolver's static presence cache).
///
/// plus a stride- sample of the single-file fixture corpus in ordinal
-/// test-name order (). The main.spy + Greeter library pair of
-/// cell 4 is lane "server"'s (CompileServerE2ETests, RegistryIsolationTests) — building a
-/// library here would cost a compile per run for a cell another test pins.
+/// test-name order ().
+///
+/// Triggers and probes (the frozen-master seam, cells 2–3 and the F1/F2 type-definition
+/// twins): a trigger defines a builtin's name — verbatim,
+/// plus a generated kind × name × position matrix (: the sibling-cell
+/// prober's generator) — and a probe () later USES the builtin in a typed position
+/// whose diagnostic differs when the master was written: b: bool = len("abc") reads
+/// len's return type (SPY0220 names int32), s._size reads whether the master
+/// str gained the block-class's backing field (SPY0203 vanishes), xs._size the same
+/// through list and object, b: bool = format(5, ">3") reads format's
+/// return type, a user @dataclass reads the master's constructor arity. Each probe's oracle is
+/// a positive control (). The regular slice carries
+/// generated triggers; the GapDiscovery mode the whole matrix.
+///
+/// The referenced-library pair (the visibility seam, cells 4–5): a tiny C# library built
+/// per sweep run by Roslyn into a per-run namespace (), and ONE
+/// main.spy importing it compiled twice — library/greeter_referenced with the reference
+/// (success) and library/greeter_unreferenced with none (SPY0300). A reference another registry
+/// loaded must stay invisible; a visibility that scans the process answers the unreferenced compile
+/// from the leaked copy. The namespace is unique per run so the unreferenced oracle, taken before any
+/// load of THIS run's library, cannot be answered by an earlier run's copy in the same process.
///
/// Schedules are seeded: thread t runs drawn from
-/// new Random(ThreadSeeds[t]) — every named program under both operations plus
-/// draws from the sample, shuffled. The seeds are literals, so a
-/// schedule is the same in every mode, every repetition and every process; no unseeded
-/// and no GetHashCode.
+/// new Random(ThreadSeeds[t]) — the library pair first, then every named program under both
+/// operations, every trigger under analyze, every probe under both operations and
+/// draws from the sample, shuffled, then every probe once more under
+/// analyze (after the schedule's last trigger). The seeds are literals, so a schedule is the same
+/// in every mode, every repetition and every process; no unseeded and no
+/// GetHashCode.
///
internal static class InProcessIsolationCorpus
{
@@ -90,31 +140,89 @@ internal static class InProcessIsolationCorpus
" drive = DriveInfo(\"/\")\n" +
" print(drive.name)\n";
+ internal const string ReferencedProgramId = "library/greeter_referenced";
+
+ internal const string UnreferencedProgramId = "library/greeter_unreferenced";
+
+ /// The code an import of a module no reference declares is refused with.
+ internal const string ModuleNotFound = "SPY0300";
+
private static readonly Lazy> NamedValue = new(BuildNamed);
private static readonly Lazy> SampleValue = new(BuildSample);
+ private static readonly Lazy> ProbesValue = new(BuildProbes);
+
+ private static readonly Lazy> GeneratedTriggersValue = new(BuildGeneratedTriggers);
+
/// The literal roster, in roster order.
internal static IReadOnlyList Named => NamedValue.Value;
/// The stride sample of the single-file fixture corpus (named entries excluded).
internal static IReadOnlyList Sample => SampleValue.Value;
+ /// The typed probes, each with its oracle control.
+ internal static IReadOnlyList Probes => ProbesValue.Value;
+
+ /// The generated trigger matrix (kind × name × position), in generator order.
+ internal static IReadOnlyList GeneratedTriggers => GeneratedTriggersValue.Value;
+
+ /// How many generated triggers the regular slice carries (the first of the matrix); the full set is the GapDiscovery mode's.
+ internal const int RegularGeneratedTriggerCount = 8;
+
+ /// The trigger programs of a mode: the verbatim texts, then the generated matrix (whole or its regular head).
+ internal static IReadOnlyList Triggers(bool full)
+ {
+ var verbatim = WarmInstanceTriggers.AllTexts
+ .Select(t => new IsolationProgram("trigger/" + t.Name, "trigger.spy", t.Text));
+ var generated = full ? GeneratedTriggers : GeneratedTriggers.Take(RegularGeneratedTriggerCount);
+ return verbatim.Concat(generated).ToList();
+ }
+
///
- /// Thread 's schedule: every named program under both operations
- /// plus (program, operation) draws from the sample, shuffled —
- /// all from new Random(ThreadSeeds[threadIndex]).
+ /// Every program id any mode can schedule — the allowlist's universe: the literal roster, the
+ /// sample, the probes, the full trigger set and the library pair's two ids (the pair's TEXT is
+ /// per run, its ids are constant). A row naming anything else is refused.
///
- internal static IReadOnlyList Schedule(int threadIndex)
+ internal static IReadOnlySet AllIds => AllIdsValue.Value;
+
+ private static readonly Lazy> AllIdsValue = new(() =>
+ Named.Concat(Sample).Concat(Probes).Concat(Triggers(full: true)).Select(p => p.Id)
+ .Concat(new[] { ReferencedProgramId, UnreferencedProgramId })
+ .ToHashSet(StringComparer.Ordinal));
+
+ /// The roster of one run: the literal roster, this run's library pair, the mode's triggers and the probes.
+ internal static IsolationRoster Roster(bool full, IReadOnlyList libraryPair)
+ => new(Named, libraryPair, Triggers(full), Probes);
+
+ ///
+ /// Thread 's schedule over : the library
+ /// pair (both operations, in roster order, first), then — shuffled on
+ /// new Random(ThreadSeeds[threadIndex]) — every named program under both operations,
+ /// every trigger under analyze, every probe under both operations and
+ /// (program, operation) draws from the sample; then every probe
+ /// under analyze, after the last trigger.
+ ///
+ internal static IReadOnlyList Schedule(int threadIndex, IsolationRoster roster)
{
var random = new Random(ThreadSeeds[threadIndex]);
var steps = new List();
- foreach (var program in Named)
+ foreach (var program in roster.LibraryPair)
+ {
+ steps.Add(new IsolationStep(program, IsolationOperation.Analyze));
+ steps.Add(new IsolationStep(program, IsolationOperation.Compile));
+ }
+
+ var head = steps.Count;
+ foreach (var program in roster.Named.Concat(roster.Probes))
{
steps.Add(new IsolationStep(program, IsolationOperation.Analyze));
steps.Add(new IsolationStep(program, IsolationOperation.Compile));
}
+ foreach (var program in roster.Triggers)
+ steps.Add(new IsolationStep(program, IsolationOperation.Analyze));
+
for (var i = 0; i < SampleDrawsPerThread; i++)
{
var program = Sample[random.Next(Sample.Count)];
@@ -122,16 +230,207 @@ internal static IReadOnlyList Schedule(int threadIndex)
steps.Add(new IsolationStep(program, operation));
}
- // Fisher–Yates on the same seeded generator.
- for (var i = steps.Count - 1; i > 0; i--)
+ // Fisher–Yates over the body, on the same seeded generator.
+ for (var i = steps.Count - 1; i > head; i--)
{
- var j = random.Next(i + 1);
+ var j = head + random.Next(i - head + 1);
(steps[i], steps[j]) = (steps[j], steps[i]);
}
+ foreach (var program in roster.Probes)
+ steps.Add(new IsolationStep(program, IsolationOperation.Analyze));
+
return steps;
}
+ // ================================================================
+ // The referenced-library pair
+ // ================================================================
+
+ ///
+ /// Builds this run's library into and returns the pair: the same
+ /// main.spy compiled with the reference (, oracle: success)
+ /// and without it (, oracle: ).
+ /// is lowercase hex unique to the run: the module is sweeplib_{tag},
+ /// the namespace Sweeplib{Tag} (the registry's PascalCase mapping of a non-system module).
+ ///
+ internal static IReadOnlyList BuildLibraryPair(string directory, string tag)
+ {
+ var moduleName = "sweeplib_" + tag;
+ var namespaceName = "Sweeplib" + char.ToUpperInvariant(tag[0]) + tag.Substring(1);
+ var assemblyName = "P54SweepLib_" + tag;
+ Directory.CreateDirectory(directory);
+ var path = Path.Combine(directory, assemblyName + ".dll");
+ var compilation = CSharpCompilation.Create(
+ assemblyName,
+ new[]
+ {
+ CSharpSyntaxTree.ParseText(
+ $"namespace {namespaceName} {{ public static class Greeter {{ public static string Hello() => \"hello\"; }} }}"),
+ },
+ IntegrationTestBase.GetSharedReferences(),
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ var emit = compilation.Emit(path);
+ if (!emit.Success)
+ throw new InvalidOperationException("The isolation library did not build (an instrument fault):\n" + string.Join("\n", emit.Diagnostics));
+
+ var source =
+ $"from {moduleName} import Greeter\n" +
+ "\n" +
+ "\n" +
+ "def main() -> None:\n" +
+ " print(Greeter.hello())\n";
+ return new[]
+ {
+ new IsolationProgram(ReferencedProgramId, "main.spy", source)
+ {
+ References = new[] { path },
+ OracleMustCarry = IsolationProgram.OracleSucceeds,
+ },
+ new IsolationProgram(UnreferencedProgramId, "main.spy", source)
+ {
+ OracleMustCarry = ModuleNotFound,
+ },
+ };
+ }
+
+ // ================================================================
+ // Triggers and probes
+ // ================================================================
+
+ private static IReadOnlyList BuildProbes()
+ {
+ IsolationProgram Probe(string name, string control, params string[] lines)
+ => new("probe/" + name, "probe.spy", string.Join("\n", lines) + "\n") { OracleMustCarry = control };
+
+ return new[]
+ {
+ Probe("builtins_ok", IsolationProgram.OracleSucceeds,
+ "def main() -> None:",
+ " print(len(\"abc\"))",
+ " print(str(5))",
+ " xs = list[int]()",
+ " xs.append(1)",
+ " print(len(xs))",
+ " d = dict[str, int]()",
+ " d[\"a\"] = 1",
+ " print(format(5, \">3\"))",
+ " o: object = 5",
+ " print(o)"),
+ Probe("len_typed", "SPY0220",
+ "def main() -> None:",
+ " b: bool = len(\"abc\")"),
+ Probe("str_no_member", "SPY0203",
+ "def main() -> None:",
+ " s = \"abc\"",
+ " print(s._size)",
+ " print(s.x)"),
+ Probe("list_no_member", "SPY0203",
+ "def main() -> None:",
+ " xs = list[int]()",
+ " print(xs._size)",
+ " print(xs.x)"),
+ Probe("exception_typed", "SPY0220",
+ "class MyErr(Exception):",
+ " pass",
+ "",
+ "def main() -> None:",
+ " try:",
+ " raise MyErr(\"x\")",
+ " except Exception as e:",
+ " b: bool = e",
+ " print(e)"),
+ Probe("format_typed", "SPY0220",
+ "def main() -> None:",
+ " b: bool = format(5, \">3\")",
+ " c: bool = print"),
+ Probe("dataclass_user", "SPY0220",
+ "@dataclass",
+ "class P:",
+ " x: int",
+ "",
+ "def main() -> None:",
+ " p = P(1)",
+ " b: bool = p.x",
+ " print(p)"),
+ Probe("str_upper_control", IsolationProgram.OracleSucceeds,
+ "def main() -> None:",
+ " s = \"abc\"",
+ " print(s.upper())"),
+ };
+ }
+
+ /// The generated matrix: kind × name × position, every cell a builtin's name defined where NameResolver declares no symbol.
+ private static IReadOnlyList BuildGeneratedTriggers()
+ {
+ var kinds = new (string Kind, Func Body)[]
+ {
+ ("class", n => new[]
+ {
+ $"class {n}:",
+ " property size: int = 0",
+ " property set size(self, v: int) -> None:",
+ " self._size = v",
+ " def __len__(self) -> int:",
+ " return 1",
+ }),
+ ("def", n => new[] { $"def {n}(x: int) -> int:", " return x" }),
+ ("dataclass", n => new[] { "@dataclass", $"class {n}:", " x: int" }),
+ ("struct", n => new[] { $"struct {n}:", " x: int" }),
+ ("interface", n => new[] { $"interface {n}:", " def probe(self) -> int: ..." }),
+ ("union", n => new[] { $"union {n}:", " case Probe(value: int)" }),
+ ("delegate", n => new[] { $"delegate {n}(value: int) -> int" }),
+ };
+ var positions = new (string Position, string[] Before, int Indent, string[] After)[]
+ {
+ ("def-body", new[] { "def helper() -> None:" }, 4, new[] { " print('helper')" }),
+ ("mod-try", new[] { "try:" }, 4, new[] { "except Exception:", " pass" }),
+ };
+ var names = new[] { "list", "format", "str", "len" };
+
+ // The regular head first: {class, def} × {list, format} × both positions — the member write
+ // through `list`/`object` and the retyped `format` the prober measured at base.
+ var cells = new List<(string Kind, string Name, string Position)>();
+ foreach (var kind in kinds.Take(2))
+ {
+ foreach (var name in names.Take(2))
+ {
+ foreach (var position in positions)
+ cells.Add((kind.Kind, name, position.Position));
+ }
+ }
+
+ foreach (var kind in kinds)
+ {
+ foreach (var name in names)
+ {
+ foreach (var position in positions)
+ {
+ if (!cells.Contains((kind.Kind, name, position.Position)))
+ cells.Add((kind.Kind, name, position.Position));
+ }
+ }
+ }
+
+ var programs = new List();
+ foreach (var (kind, name, position) in cells)
+ {
+ var body = kinds.First(k => k.Kind == kind).Body(name);
+ var layout = positions.First(p => p.Position == position);
+ var lines = layout.Before
+ .Concat(body.Select(l => new string(' ', layout.Indent) + l))
+ .Concat(layout.After)
+ .Concat(new[] { "", "def main() -> None:", " print('ok')" });
+ programs.Add(new IsolationProgram($"trigger/{kind}_{name}_{position}", "trigger.spy", string.Join("\n", lines) + "\n"));
+ }
+
+ return programs;
+ }
+
+ // ================================================================
+ // The literal roster and the sample
+ // ================================================================
+
private static IReadOnlyList BuildNamed()
{
var programs = new List { RegenerateCell1Program() };
diff --git a/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationSweepTests.cs b/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationSweepTests.cs
index 3ce643ac6..420fd345b 100644
--- a/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationSweepTests.cs
+++ b/src/Sharpy.Compiler.Tests/Conformance/InProcessIsolationSweepTests.cs
@@ -1,10 +1,12 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
+using System.Text.RegularExpressions;
using Sharpy.Compiler.Diagnostics;
using Sharpy.Compiler.Logging;
using Sharpy.TestInfrastructure;
using Sharpy.TestInfrastructure.Integration;
+using Sharpy.TestInfrastructure.Registry;
using Xunit;
using Xunit.Abstractions;
@@ -33,7 +35,15 @@ namespace Sharpy.Compiler.Tests.Conformance;
/// not reference the LSP handlers.
///
/// Oracle and fingerprint. Before any concurrent phase, every (instance kind, program,
-/// operation) a schedule names runs ALONE on the test thread on a FRESH instance. A fingerprint is
+/// operation) a schedule names runs ALONE on the test thread on a FRESH instance — the programs that
+/// name no reference FIRST, so the oracle of library/greeter_unreferenced is taken before this
+/// run's library has been loaded by anything in the process (its namespace is unique to the run, so
+/// no earlier run's copy can answer it either); then a bystander holding the
+/// library stays alive through the concurrent phase — the plan's cell-4 shape, "another registry in
+/// the process has loaded it". Every program with an
+/// control is checked against its oracle on every (kind, operation): the probes read what the seams
+/// decide (SPY0220/SPY0203 on a fresh instance; success for the referenced compile; SPY0300 for the
+/// unreferenced one), or the run is an instrument fault, not a green. A fingerprint is
/// success + the sorted multiset of diagnostic (code, line, col) + the used-assembly set
/// where the operation exposes one (compile on both kinds —
/// and the 's registry — and analyze on ;
@@ -53,7 +63,7 @@ namespace Sharpy.Compiler.Tests.Conformance;
/// A cell is red in a repetition when any of its observations in that repetition is; k/R is
/// the number of red repetitions.
///
-/// Ratchet. Rows program sharing threads operation bucket [probabilistic] # #2250
+/// Ratchet. Rows program sharing threads operation bucket [probabilistic] # #NNNN
/// reason; reproduced k/R @ sha in Conformance/in-process-isolation-allowlist.txt. A red
/// cell without a row fails; a row whose cell was exercised and reproduced in no repetition fails as
/// stale (drain on fix) unless it is marked probabilistic — those are reported, not judged. A
@@ -65,7 +75,13 @@ public class InProcessIsolationSweepTests
{
private const string AllowlistFileName = "in-process-isolation-allowlist.txt";
private const string FullModeVariable = "SHARPY_ISOLATION_SWEEP_FULL";
- private const string IssueCite = "#2250";
+ ///
+ /// A row cites an issue by number (#NNNN) — any issue, so the ratchet outlives the class tracker it was
+ /// born under (#2250 is closed): whether the cited issue is still OPEN is build_tools/check_allowlist_issue_state.sh's
+ /// question, asked of every tracked allowlist file.
+ ///
+ private static readonly Regex IssueCitePattern = new(@"(^|\s)#\d+\b", RegexOptions.Compiled);
+ private const string IssueCite = "#NNNN";
private const string Probabilistic = "probabilistic";
internal const string Drift = "drift";
@@ -124,15 +140,15 @@ private enum InstanceKind
[Fact]
[Trait("Category", "GapDiscovery")]
public void Sweep_EightThreads_EightRepetitions()
- => RunSweep("gap-discovery", new[] { 8 }, repetitions: 8, "in-process-isolation-report.json");
+ => RunSweep("gap-discovery", new[] { 8 }, repetitions: 8, fullTriggers: true, "in-process-isolation-report.json");
[Fact]
public void Sweep_RegularSlice()
{
if (Environment.GetEnvironmentVariable(FullModeVariable) == "1")
- RunSweep("full", new[] { 1, 2, 8 }, repetitions: 8, "in-process-isolation-slice-report.json");
+ RunSweep("full", new[] { 1, 2, 8 }, repetitions: 8, fullTriggers: true, "in-process-isolation-slice-report.json");
else
- RunSweep("regular", new[] { 1, 2 }, repetitions: 1, "in-process-isolation-slice-report.json");
+ RunSweep("regular", new[] { 1, 2 }, repetitions: 1, fullTriggers: false, "in-process-isolation-slice-report.json");
}
// ================================================================
@@ -180,29 +196,60 @@ public void Oracle_DiscriminatesPrograms_PositionsAndBuckets()
}
}
- /// The schedules are pure functions of the literal seeds, and every thread's schedule
- /// carries every named program under both operations.
+ /// The schedules are pure functions of the literal seeds; every thread's schedule opens
+ /// with the library pair, carries every named program and probe under both operations and every
+ /// trigger under analyze, and reads every probe once more after its last trigger; the regular
+ /// trigger set is a head of the full one; every probe and the pair carry an oracle control.
[Fact]
public void Schedules_AreSeededAndCarryTheRoster()
{
Assert.Equal(8, InProcessIsolationCorpus.ThreadSeeds.Distinct().Count());
Assert.StartsWith(InProcessIsolationCorpus.Cell1FirstLine, InProcessIsolationCorpus.RegenerateCell1Program().Source, StringComparison.Ordinal);
Assert.True(InProcessIsolationCorpus.Sample.Count > 100, $"stride sample has {InProcessIsolationCorpus.Sample.Count} programs");
- for (var t = 0; t < InProcessIsolationCorpus.ThreadSeeds.Length; t++)
+
+ var pair = new[]
{
- var first = InProcessIsolationCorpus.Schedule(t).Select(s => $"{s.Program.Id}/{s.Operation}").ToList();
- var second = InProcessIsolationCorpus.Schedule(t).Select(s => $"{s.Program.Id}/{s.Operation}").ToList();
- Assert.Equal(first, second);
- foreach (var program in InProcessIsolationCorpus.Named)
+ new IsolationProgram(InProcessIsolationCorpus.ReferencedProgramId, "main.spy", "") { References = new[] { "x.dll" }, OracleMustCarry = IsolationProgram.OracleSucceeds },
+ new IsolationProgram(InProcessIsolationCorpus.UnreferencedProgramId, "main.spy", "") { OracleMustCarry = InProcessIsolationCorpus.ModuleNotFound },
+ };
+ var regular = InProcessIsolationCorpus.Roster(full: false, pair);
+ var full = InProcessIsolationCorpus.Roster(full: true, pair);
+ Assert.Equal(WarmInstanceTriggers.AllTexts.Length + InProcessIsolationCorpus.RegularGeneratedTriggerCount, regular.Triggers.Count);
+ Assert.Equal(regular.Triggers.Select(t => t.Id), full.Triggers.Take(regular.Triggers.Count).Select(t => t.Id));
+ Assert.True(full.Triggers.Count > regular.Triggers.Count, $"full carries {full.Triggers.Count} triggers");
+ Assert.Equal(full.Triggers.Count, full.Triggers.Select(t => t.Id).Distinct().Count());
+ Assert.All(full.Probes.Concat(pair), p => Assert.NotNull(p.OracleMustCarry));
+ Assert.Contains(full.Probes, p => p.OracleMustCarry == "SPY0220");
+ Assert.Contains(full.Probes, p => p.OracleMustCarry == "SPY0203");
+
+ foreach (var roster in new[] { regular, full })
+ {
+ for (var t = 0; t < InProcessIsolationCorpus.ThreadSeeds.Length; t++)
{
- Assert.Contains($"{program.Id}/{IsolationOperation.Analyze}", first);
- Assert.Contains($"{program.Id}/{IsolationOperation.Compile}", first);
+ var first = InProcessIsolationCorpus.Schedule(t, roster).Select(s => $"{s.Program.Id}/{s.Operation}").ToList();
+ var second = InProcessIsolationCorpus.Schedule(t, roster).Select(s => $"{s.Program.Id}/{s.Operation}").ToList();
+ Assert.Equal(first, second);
+ Assert.Equal(
+ pair.SelectMany(p => new[] { $"{p.Id}/{IsolationOperation.Analyze}", $"{p.Id}/{IsolationOperation.Compile}" }),
+ first.Take(4));
+ foreach (var program in roster.Named.Concat(roster.Probes))
+ {
+ Assert.Contains($"{program.Id}/{IsolationOperation.Analyze}", first);
+ Assert.Contains($"{program.Id}/{IsolationOperation.Compile}", first);
+ }
+
+ var lastTrigger = first.FindLastIndex(s => s.StartsWith("trigger/", StringComparison.Ordinal));
+ Assert.True(lastTrigger >= 0, "the schedule carries no trigger");
+ foreach (var program in roster.Triggers)
+ Assert.Contains($"{program.Id}/{IsolationOperation.Analyze}", first);
+ foreach (var program in roster.Probes)
+ Assert.Contains($"{program.Id}/{IsolationOperation.Analyze}", first.Skip(lastTrigger + 1));
}
}
Assert.NotEqual(
- InProcessIsolationCorpus.Schedule(0).Select(s => s.Program.Id),
- InProcessIsolationCorpus.Schedule(1).Select(s => s.Program.Id));
+ InProcessIsolationCorpus.Schedule(0, regular).Select(s => s.Program.Id),
+ InProcessIsolationCorpus.Schedule(1, regular).Select(s => s.Program.Id));
}
// ================================================================
@@ -213,37 +260,100 @@ private sealed record Observation(int Repetition, Sharing Sharing, int Threads,
private sealed record RedCell(string Key, string Bucket, int Reds, int Repetitions, string Oracle, string Observed);
- private void RunSweep(string mode, int[] threadCounts, int repetitions, string reportFileName)
+ private void RunSweep(string mode, int[] threadCounts, int repetitions, bool fullTriggers, string reportFileName)
{
var wall = Stopwatch.StartNew();
var maxThreads = threadCounts.Max();
- var schedules = Enumerable.Range(0, maxThreads).Select(InProcessIsolationCorpus.Schedule).ToList();
+ var runTag = Guid.NewGuid().ToString("N").Substring(0, 8);
+ var libraryDir = Path.Combine(Path.GetTempPath(), $"sharpy_isolation_lib_{runTag}");
+ try
+ {
+ var roster = InProcessIsolationCorpus.Roster(fullTriggers, InProcessIsolationCorpus.BuildLibraryPair(libraryDir, runTag));
+ var schedules = Enumerable.Range(0, maxThreads).Select(t => InProcessIsolationCorpus.Schedule(t, roster)).ToList();
+
+ // 1. The solo oracle, before any concurrent phase: a fresh instance per (kind, program, operation) —
+ // reference-free programs first, so the unreferenced library compile is answered before this run's
+ // library has been loaded by anything in the process.
+ var oracle = new Dictionary<(InstanceKind, string, IsolationOperation), Fingerprint>();
+ foreach (var referenced in new[] { false, true })
+ {
+ foreach (var kind in new[] { InstanceKind.Compiler, InstanceKind.Api })
+ {
+ foreach (var step in schedules.SelectMany(s => s).Where(s => (s.Program.References.Count > 0) == referenced))
+ {
+ var key = (kind, step.Program.Id, step.Operation);
+ if (!oracle.ContainsKey(key))
+ oracle[key] = Solo(kind, step);
+ }
+ }
+ }
- // 1. The solo oracle, before any concurrent phase: a fresh instance per (kind, program, operation).
- var oracle = new Dictionary<(InstanceKind, string, IsolationOperation), Fingerprint>();
- foreach (var kind in new[] { InstanceKind.Compiler, InstanceKind.Api })
+ var oracleSeconds = wall.Elapsed.TotalSeconds;
+ AssertOracleControls(oracle, roster);
+
+ // The bystander: a registry that has loaded this run's library and lives through the concurrent phase.
+ using var bystander = IsolationInstance.Create(InstanceKind.Compiler);
+ var bystanderReading = bystander.Observe(new IsolationStep(roster.LibraryPair[0], IsolationOperation.Analyze));
+ Assert.True(bystanderReading.Success, $"the bystander's referenced analysis must succeed: {bystanderReading}");
+
+ // 2. The concurrent phase: repetitions × sharing × threads, each a barrier-started set of workers.
+ var observations = new List();
+ for (var repetition = 0; repetition < repetitions; repetition++)
+ {
+ foreach (var sharing in AllSharing)
+ {
+ foreach (var threads in threadCounts)
+ observations.AddRange(RunConfiguration(repetition, sharing, threads, schedules));
+ }
+ }
+
+ Judge(mode, threadCounts, repetitions, reportFileName, roster, oracle, observations, oracleSeconds, wall);
+ }
+ finally
{
- foreach (var step in schedules.SelectMany(s => s))
+ try
{
- var key = (kind, step.Program.Id, step.Operation);
- if (!oracle.ContainsKey(key))
- oracle[key] = Solo(kind, step);
+ if (Directory.Exists(libraryDir))
+ Directory.Delete(libraryDir, recursive: true);
+ }
+ catch
+ {
+ // Best-effort cleanup; the loaded library may still be mapped.
}
}
+ }
- var oracleSeconds = wall.Elapsed.TotalSeconds;
-
- // 2. The concurrent phase: repetitions × sharing × threads, each a barrier-started set of workers.
- var observations = new List();
- for (var repetition = 0; repetition < repetitions; repetition++)
+ ///
+ /// Every oracle control holds on every (kind, operation): a probe's fresh answer carries the code
+ /// it reads for, the referenced compile succeeds, the unreferenced one is refused — else the
+ /// probe is not reading what the seam decides and the run is an instrument fault.
+ ///
+ private static void AssertOracleControls(IReadOnlyDictionary<(InstanceKind, string, IsolationOperation), Fingerprint> oracle, IsolationRoster roster)
+ {
+ var problems = new List();
+ foreach (var program in roster.Probes.Concat(roster.LibraryPair).Where(p => p.OracleMustCarry != null))
{
- foreach (var sharing in AllSharing)
+ foreach (var kind in new[] { InstanceKind.Compiler, InstanceKind.Api })
{
- foreach (var threads in threadCounts)
- observations.AddRange(RunConfiguration(repetition, sharing, threads, schedules));
+ foreach (var operation in new[] { IsolationOperation.Analyze, IsolationOperation.Compile })
+ {
+ var fingerprint = oracle[(kind, program.Id, operation)];
+ var holds = program.OracleMustCarry == IsolationProgram.OracleSucceeds
+ ? fingerprint.Success && fingerprint.Exception == null
+ : !fingerprint.Success && fingerprint.Diagnostics.Contains(program.OracleMustCarry + "@", StringComparison.Ordinal);
+ if (!holds)
+ problems.Add($"{program.Id} {kind}/{Name(operation)}: the solo oracle must carry {program.OracleMustCarry}; observed {fingerprint}");
+ }
}
}
+ Assert.True(problems.Count == 0, "oracle control(s) failed (an instrument fault, not a cell):\n " + string.Join("\n ", problems));
+ }
+
+ private void Judge(string mode, int[] threadCounts, int repetitions, string reportFileName, IsolationRoster roster,
+ Dictionary<(InstanceKind, string, IsolationOperation), Fingerprint> oracle, List observations, double oracleSeconds, Stopwatch wall)
+ {
+
// 3. Classification: a cell is (program, sharing, threads, operation); red in a repetition when any observation is.
var exercised = new HashSet(StringComparer.Ordinal);
var redByKey = new Dictionary>(StringComparer.Ordinal);
@@ -278,7 +388,7 @@ private void RunSweep(string mode, int[] threadCounts, int repetitions, string r
.ToList();
// 4. The ratchet.
- var allowlist = LoadAllowlist();
+ var allowlist = LoadAllowlist(InProcessIsolationCorpus.AllIds);
var unlisted = reds.Where(r => !allowlist.ContainsKey(r.Key)).ToList();
var notExercised = allowlist.Keys.Where(k => !exercised.Contains(CellOf(k))).OrderBy(k => k, StringComparer.Ordinal).ToList();
var silent = allowlist
@@ -290,7 +400,7 @@ private void RunSweep(string mode, int[] threadCounts, int repetitions, string r
var bucketCounts = new[] { Drift, Crash, Order }.ToDictionary(b => b, b => reds.Count(r => r.Bucket == b));
var census = $"ISOLATION-CENSUS mode={mode} threads={{{string.Join(",", threadCounts)}}} R={repetitions} "
- + $"corpus={InProcessIsolationCorpus.Named.Count}+{InProcessIsolationCorpus.Sample.Count} "
+ + $"corpus={roster.Named.Count}+{roster.LibraryPair.Count}+{roster.Triggers.Count}+{roster.Probes.Count}+{InProcessIsolationCorpus.Sample.Count} "
+ $"oracle={oracle.Count} observations={observations.Count} compared={observed} cancelled={cancelled} "
+ $"cells={exercised.Count} red={reds.Count} drift={bucketCounts[Drift]} crash={bucketCounts[Crash]} order={bucketCounts[Order]} "
+ $"allowlisted={allowlist.Count} oracle-elapsed={oracleSeconds:F0}s elapsed={wall.Elapsed.TotalSeconds:F0}s";
@@ -478,44 +588,62 @@ public void Dispose()
///
/// One for the instance's life — its module registry is built once, in the
- /// constructor, and serves every program of the schedule. Sharpy.Core is referenced (without it
- /// the C# → IL emit of print(1) is SPY0908 — measured), and the output path makes
- /// Compile reach IL.
+ /// constructor, and serves every reference-free program of the schedule; a program that names a
+ /// reference gets a sibling on the same instance (one per distinct
+ /// reference set, built on first use — a binds its references at
+ /// construction), so the leak the library pair guards flows between two registries on one
+ /// thread as well as across threads. Sharpy.Core is referenced (without it the C# → IL emit of
+ /// print(1) is SPY0908 — measured), and the output path makes Compile reach IL.
///
private sealed class CompilerInstance : IsolationInstance
{
- private readonly Compiler _compiler;
+ private readonly Dictionary _compilers = new(StringComparer.Ordinal);
+ private readonly object _gate = new();
public CompilerInstance()
{
Directory.CreateDirectory(Root);
- _compiler = new Compiler(CompilerOptionsFactory.ForCli(
- references: new[] { SharpyCoreReference.Location },
+ _compilers[""] = Build(Array.Empty());
+ }
+
+ private Compiler Build(IReadOnlyList references)
+ => new(CompilerOptionsFactory.ForCli(
+ references: new[] { SharpyCoreReference.Location }.Concat(references).ToArray(),
assemblyName: AssemblyName,
outputAssemblyPath: Path.Combine(Root, AssemblyName + ".dll")), NullLogger.Instance);
+
+ private Compiler For(IsolationProgram program)
+ {
+ var key = string.Join(";", program.References);
+ lock (_gate)
+ {
+ if (!_compilers.TryGetValue(key, out var compiler))
+ _compilers[key] = compiler = Build(program.References);
+ return compiler;
+ }
}
protected override Fingerprint Analyze(IsolationProgram program, CancellationToken cancellationToken)
{
- var result = _compiler.Analyze(program.Source, program.FileName, cancellationToken);
+ var result = For(program).Analyze(program.Source, program.FileName, cancellationToken);
return Fingerprint.Of(result.Success, result.Diagnostics.GetAll(), result.ModuleRegistry?.GetUsedAssemblyPaths());
}
protected override Fingerprint Compile(IsolationProgram program, CancellationToken cancellationToken)
{
- var result = _compiler.Compile(program.Source, program.FileName, cancellationToken);
+ var result = For(program).Compile(program.Source, program.FileName, cancellationToken);
return Fingerprint.Of(result.Success, result.Diagnostics.GetAll(), result.ModuleRegistry?.GetUsedAssemblyPaths());
}
}
- /// One referencing Sharpy.Core; each Compile writes into its own directory.
+ /// One referencing Sharpy.Core; a program's own references ride on its options; each Compile writes into its own directory.
private sealed class ApiInstance : IsolationInstance
{
private readonly CompilerApi _api = new(NullLogger.Instance, new[] { SharpyCoreReference.Location });
protected override Fingerprint Analyze(IsolationProgram program, CancellationToken cancellationToken)
{
- var result = _api.AnalyzeDocument(program.Source, program.FileName, CompilerOptionsFactory.ForCli(),
+ var result = _api.AnalyzeDocument(program.Source, program.FileName, CompilerOptionsFactory.ForCli(references: program.References.ToArray()),
stageMetrics: null, cancellationToken);
return Fingerprint.Of(result.Success, result.Diagnostics, usedAssemblies: null);
}
@@ -526,6 +654,7 @@ protected override Fingerprint Compile(IsolationProgram program, CancellationTok
try
{
var options = CompilerOptionsFactory.ForCli(
+ references: program.References.ToArray(),
assemblyName: AssemblyName,
outputAssemblyPath: Path.Combine(callDir, AssemblyName + ".dll"));
var result = _api.Compile(program.Source, options, program.FileName, cancellationToken);
@@ -553,11 +682,12 @@ protected override Fingerprint Compile(IsolationProgram program, CancellationTok
private sealed record AllowlistRow(bool Probabilistic, string Comment);
///
- /// program sharing threads operation bucket [probabilistic] # #2250 reason; reproduced k/R @ sha.
- /// A row that does not parse, names an unknown sharing/operation/bucket, or does not cite #2250 is
- /// refused (an instrument fault), never silently skipped.
+ /// program sharing threads operation bucket [probabilistic] # #NNNN reason; reproduced k/R @ sha.
+ /// A row that does not parse, names a program that is no corpus id (:
+ /// a typo would park a red cell as "not exercised"), names an unknown sharing/operation/bucket, or
+ /// cites no issue number is refused (an instrument fault), never silently skipped.
///
- private static Dictionary LoadAllowlist()
+ private static Dictionary LoadAllowlist(IReadOnlySet corpusIds)
{
var path = Path.Combine(FixtureRoots.RepositoryRoot, "src", "Sharpy.Compiler.Tests", "Conformance", AllowlistFileName);
if (!File.Exists(path))
@@ -577,11 +707,13 @@ private static Dictionary LoadAllowlist()
var probabilistic = fields.Length == 6 && fields[5] == Probabilistic;
if (fields.Length != 5 && !probabilistic)
throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{line}' — expected `program sharing threads operation bucket [{Probabilistic}] # {IssueCite} …`.");
+ if (!corpusIds.Contains(fields[0]))
+ throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{line}' — '{fields[0]}' is not a corpus program id (InProcessIsolationCorpus.AllIds).");
if (!sharingNames.Contains(fields[1]) || fields[2] is not ("1" or "2" or "8") || !operationNames.Contains(fields[3])
|| fields[4] is not (Drift or Crash or Order))
throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{line}' — unknown sharing, thread count, operation or bucket.");
- if (!comment.Contains(IssueCite, StringComparison.Ordinal))
- throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{line}' — every row cites {IssueCite}.");
+ if (!IssueCitePattern.IsMatch(comment))
+ throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{line}' — every row cites an issue by number ({IssueCite}).");
var key = string.Join(' ', fields.Take(5));
if (!rows.TryAdd(key, new AllowlistRow(probabilistic, comment)))
throw new InvalidOperationException($"Conformance/{AllowlistFileName}: '{key}' is listed twice.");
diff --git a/src/Sharpy.Compiler.Tests/Conformance/ProcessWideResolutionScanTests.cs b/src/Sharpy.Compiler.Tests/Conformance/ProcessWideResolutionScanTests.cs
index 8a88373d4..d72722fb6 100644
--- a/src/Sharpy.Compiler.Tests/Conformance/ProcessWideResolutionScanTests.cs
+++ b/src/Sharpy.Compiler.Tests/Conformance/ProcessWideResolutionScanTests.cs
@@ -89,9 +89,9 @@ public class ProcessWideResolutionScanTests
"assembly qualifier binds against the visible set by simple name and an unqualified name is " +
"searched in the visible set, so nothing binds against the process."),
["Discovery/AssemblyVisibility.cs::AssemblyVisibility.LoadFrameworkAssembly::Assembly.Load"] = (1,
- "the one seam: Assembly.Load(AssemblyName) of a trusted-platform System.*/Microsoft.*/netstandard/" +
- "mscorlib name (or a compiler dependency), once per process behind the framework Lazy — the " +
- "default context's TPA binding, a process constant."),
+ "the one seam: Assembly.Load(AssemblyName) of a shared-framework name (a .dll in the " +
+ "Microsoft.NETCore.App runtime directory CoreLib was loaded from) or Sharpy.Core, once per " +
+ "process behind the framework Lazy — the default context's TPA binding, a process constant."),
["Discovery/AssemblyVisibility.cs::AssemblyVisibility.IsProcessStable::AssemblyLoadContext.Default"] = (1,
"the one seam: Design Decision 3's gate — a static Type-keyed cache holds a type only when it is " +
"wholly in the default context, so no static cache pins a per-registry load context."),
diff --git a/src/Sharpy.Compiler.Tests/Conformance/TpaNamespaceGateTests.cs b/src/Sharpy.Compiler.Tests/Conformance/TpaNamespaceGateTests.cs
index 43ca0d52e..27203fe7f 100644
--- a/src/Sharpy.Compiler.Tests/Conformance/TpaNamespaceGateTests.cs
+++ b/src/Sharpy.Compiler.Tests/Conformance/TpaNamespaceGateTests.cs
@@ -84,7 +84,7 @@ public void KnownRed_RowsNameRealCellsAndCiteTheIssue()
foreach (var (cell, reason) in KnownRed)
{
Assert.Contains(cell, AllCells);
- Assert.Contains("#2250", reason, StringComparison.Ordinal);
+ Assert.Matches(@"(^|\s)#\d+\b", reason);
}
Assert.True(AllCells.Count(c => !KnownRed.ContainsKey(c)) >= 2,
"each probe needs a green positive-control cell; the oracle is not 'always red'");
diff --git a/src/Sharpy.Compiler.Tests/Conformance/in-process-isolation-allowlist.txt b/src/Sharpy.Compiler.Tests/Conformance/in-process-isolation-allowlist.txt
index 767b793da..e3c1820c2 100644
--- a/src/Sharpy.Compiler.Tests/Conformance/in-process-isolation-allowlist.txt
+++ b/src/Sharpy.Compiler.Tests/Conformance/in-process-isolation-allowlist.txt
@@ -1,6 +1,6 @@
# In-process isolation sweep allowlist (P54, #2250) — read by InProcessIsolationSweepTests.
#
-# One row per red cell: `program sharing threads operation bucket [probabilistic] # #2250 reason; reproduced k/R @ sha`
+# One row per red cell: `program sharing threads operation bucket [probabilistic] # #NNNN reason; reproduced k/R @ sha`
# program a corpus id (InProcessIsolationCorpus: a named roster id or a fixture test name)
# sharing compiler-per-thread | api-per-thread | api-shared
# threads 1 | 2 | 8
@@ -10,4 +10,5 @@
#
# A red cell without a row fails. A row whose cell was exercised and reproduced in no repetition fails
# as stale — drain it when the fix lands — unless it is marked `probabilistic` (reported, not judged).
-# Every row cites #2250; the k/R is the measured reproduction count at the named sha.
+# Every row cites an OPEN issue by number (check_allowlist_issue_state.sh gates the state); the k/R is the measured
+# reproduction count at the named sha.
diff --git a/src/Sharpy.Compiler.Tests/Diagnostics/CrashBundleExclusivityTests.cs b/src/Sharpy.Compiler.Tests/Diagnostics/CrashBundleExclusivityTests.cs
index 7a3874f01..7e4e4ca65 100644
--- a/src/Sharpy.Compiler.Tests/Diagnostics/CrashBundleExclusivityTests.cs
+++ b/src/Sharpy.Compiler.Tests/Diagnostics/CrashBundleExclusivityTests.cs
@@ -85,7 +85,7 @@ public void ConcurrentWriters_AtOneClockTick_EachCrashGetsItsOwnDirectoryAndRepo
public void KnownRedRows_CiteTheIssue()
{
foreach (var (_, reason) in KnownRed)
- Assert.Contains("#2250", reason, StringComparison.Ordinal);
+ Assert.Matches(@"(^|\s)#\d+\b", reason);
}
private static Outcome RunWriters(int writers, string outputDirectory)
diff --git a/src/Sharpy.Compiler.Tests/Diagnostics/StaticStateConformanceTests.cs b/src/Sharpy.Compiler.Tests/Diagnostics/StaticStateConformanceTests.cs
index bcdd30dc4..4d36b338e 100644
--- a/src/Sharpy.Compiler.Tests/Diagnostics/StaticStateConformanceTests.cs
+++ b/src/Sharpy.Compiler.Tests/Diagnostics/StaticStateConformanceTests.cs
@@ -201,10 +201,10 @@ public StaticStateConformanceTests(ITestOutputHelper output)
// ---- The visibility seam's framework index (#2250, P54 Design Decision 1) ----
[("Sharpy.Compiler.Discovery.AssemblyVisibility", "s_framework")] =
- "Lazy with no key: the framework set — the trusted-platform System.*/Microsoft.*/" +
- "netstandard/mscorlib assemblies, Sharpy.Core, the compiler and its direct dependencies — loaded " +
- "and indexed once; the trusted-platform list is fixed at process start, so the value is a process " +
- "constant. ExecutionAndPublication: a reader blocks until the index is complete. The five " +
+ "Lazy with no key: the framework set — the shared framework (every trusted-platform " +
+ "assembly whose .dll lies in the Microsoft.NETCore.App runtime directory CoreLib was loaded from) " +
+ "plus Sharpy.Core, never the host's own application packages (f5fb7f8e4) — loaded and indexed " +
+ "once; CoreLib's directory is fixed at process start, so the value is a process constant. ExecutionAndPublication: a reader blocks until the index is complete. The five " +
"name-keyed rows it replaced (_originNameDefinitionCache, _displayNameDefinitionCache, " +
"s_typePresentCache, _probedNamespaces, _loadedRuntimeNamespaces) drained into per-visibility state.",
};
diff --git a/src/Sharpy.Compiler/CodeGen/RoslynEmitter.CompilationUnit.cs b/src/Sharpy.Compiler/CodeGen/RoslynEmitter.CompilationUnit.cs
index 6f662db0b..bd5968901 100644
--- a/src/Sharpy.Compiler/CodeGen/RoslynEmitter.CompilationUnit.cs
+++ b/src/Sharpy.Compiler/CodeGen/RoslynEmitter.CompilationUnit.cs
@@ -587,8 +587,8 @@ private static string ResolveClrTypeName(Symbol? symbol, string fallbackName)
///
private static bool IsClrTypeGenericInNamespace(string namespaceName, string typeName)
{
- // Rule-2 cell (#2250 findings ledger; no tracker yet): this is the emitter asking reflection a
- // semantic question, which belongs in semantic analysis as a materialized fact. Until it moves,
+ // TODO(#2320): Rule-2 cell — this is the emitter asking reflection a semantic question, which
+ // belongs in semantic analysis as a materialized fact (CodeGenInfo). Until it moves,
// it asks the FRAMEWORK visibility — a process constant — instead of every assembly the process
// has loaded; its only caller passes a .NET framework namespace (IsNetFrameworkNamespace).
var framework = Discovery.AssemblyVisibility.FrameworkOnly;
diff --git a/src/Sharpy.Lsp.Tests/Conformance/WarmInstanceParityTests.cs b/src/Sharpy.Lsp.Tests/Conformance/WarmInstanceParityTests.cs
index b3da49c03..f657c2a05 100644
--- a/src/Sharpy.Lsp.Tests/Conformance/WarmInstanceParityTests.cs
+++ b/src/Sharpy.Lsp.Tests/Conformance/WarmInstanceParityTests.cs
@@ -154,7 +154,7 @@ public void KnownRedRows_NameCellsOfTheMatrix()
{
Assert.True(cells.Contains(cell), $"KnownRed row '{cell}' names no cell of the matrix");
Assert.False(cell.Contains($" {Fresh} ", StringComparison.Ordinal), $"'{cell}': the fresh arm is the positive control; it carries no KnownRed row");
- Assert.Contains("#2250", row.Row, StringComparison.Ordinal);
+ Assert.Matches(@"(^|\s)#\d+\b", row.Row);
Assert.Matches(@"\d+/\d+[^@]* @ [0-9a-f]{7,}", row.Row);
}
}
diff --git a/src/Sharpy.Stdlib.Tests/Conformance/StdlibModuleGlobalConcurrencyTests.cs b/src/Sharpy.Stdlib.Tests/Conformance/StdlibModuleGlobalConcurrencyTests.cs
index 2803fc195..071fc6e92 100644
--- a/src/Sharpy.Stdlib.Tests/Conformance/StdlibModuleGlobalConcurrencyTests.cs
+++ b/src/Sharpy.Stdlib.Tests/Conformance/StdlibModuleGlobalConcurrencyTests.cs
@@ -120,7 +120,7 @@ public void EverySkippedOperation_NamesItsReason()
Exercisers.Where(e => e.NotApplicable.Count == 0).Select(e => e.Module).OrderBy(m => m, StringComparer.Ordinal));
}
- /// Every KnownRed row names a real cell and cites #2250 with a measured reproduction.
+ /// Every KnownRed row names a real cell and cites an issue by number with a measured reproduction.
[Fact]
public void EveryKnownRedRow_IsARealCellCitingTheIssue()
{
@@ -130,7 +130,7 @@ public void EveryKnownRedRow_IsARealCellCitingTheIssue()
foreach (var (cell, row) in KnownRed)
{
Assert.Contains(cell, cells);
- Assert.Contains("#2250", row.Evidence, StringComparison.Ordinal);
+ Assert.Matches(@"(^|\s)#\d+\b", row.Evidence);
Assert.Contains(" @ ", row.Evidence, StringComparison.Ordinal);
}
}