Repository navigation
Merged
Conversation
…r and repr print <mod.C object> and never iterate it (refs #2057) R-HO (a). Str(object) and Repr(object) ran the #1453 plain-sequence arm before the R-CF instance fallback, so a user class with __iter__ (emitted as IEnumerable<T>; with __next__ also as its own IEnumerator<T>) was formatted by enumerating it: `['item', 'item']` from str, `[]` from every later site, and a loop afterwards saw 0 items (per the 2026-10-15 cell, an endless __next__ hangs print). Both now run one Sharpy-instance arm, TryFormatSharpyInstance (Str.cs), before the sequence arm — one predicate, two callers. The predicate is the compiler's stamp (new PyFormat.IsSharpyStamped: a SharpyName/SharpyModule/SharpyModuleType attribute on the type or its declaring chain) and not [CompilerGenerated], then the existing TryFormatInstanceFallback. Not the fallback's wider IsSharpyDeclared: its "nested in a static class" rule also matches LINQ's Enumerable+IteratorSelectIterator, and a first cut with it moved bare_clr_sequence_display_1453 (`['c', 'b', 'a']` -> `<__main__.IteratorSelectIterator object>`). The [CompilerGenerated] exclusion keeps a generator's state machine (nested in its stamped declaring type) on the #1453 arm. IsSharpyDeclared is re-expressed as IsSharpyStamped || nested-in-static (same truth table), so the attribute triple has one spelling. Matrix (Core.Tests SharpyInstanceFormattingTests, C# classes mirroring `emit csharp` output): dunders {__iter__+__next__, +__str__, +__repr__, __iter__ as a generator, __len__+__getitem__, none} x site {str, repr, print, f-string hole (PyFormat.Apply ""), list element} = 30 rows, each also asserting a loop afterwards still sees 2 items (or the class is not enumerable). The +__str__ repr and list-element rows assert only the no-iteration half (python: <__main__.Counter object>; still `Counter!` until R-CP). Plus 2 negative controls: a generator function's result and a LINQ select iterator keep `[1, 2]` / `['c', 'b', 'a']`. Run fixture builtins/iterable_instance_str_2057 (.spy + .expected), loop counts 2 and 3, python3 3.12 output modulo the address. Mutations (cp backup, shutil.copy + utime restore, rebuild): - M1 swap the arms back (delete the Sharpy-instance arm in Str and Repr): theory 10 red / 22 green (exactly iter-next x5 + iter-generator x5); fixture iterable_instance_str_2057 red, bare_clr_sequence_display_1453 green. - M2 drop the [CompilerGenerated] exclusion: GeneratorFunctionResult_KeepsThePlainSequenceRendering red, 31 green. - M3 drop the stamp test (wider nested-in-static rule): LinqIteratorNestedInStaticClass_KeepsThePlainSequenceRendering red, 31 green; fixture bare_clr_sequence_display_1453 red. - Restored: theory 32/0/0; the two fixtures 2/0/0. Direction (`sharpyc run`; base = this worktree's binary @ e7e0a0e before editing; after = this commit's tree): - __iter__/__next__ only, sites str/repr/print/f"{c}"/[c] + loop: `['item', 'item']` `[]` `[]` `[]` `[[]]` / 0 -> `<__main__.Counter object>` x4, `[<__main__.Counter object>]` / 2. - __iter__ as a generator: `[1, 2]` x4, `[[1, 2]]` / 2 -> `<__main__.Bag object>` x4, `[<__main__.Bag object>]` / 2. - unchanged: +__str__ (`Counter!`), +__repr__ (`Counter()`), plain and __len__+__getitem__ (`<__main__.X object>`), a generator function (`[1, 2]`), map (`<map object>`), a generator expression (`<iterator object>`), `[ch for ch in reversed("abc")]`. - stdlib, same class (base read under M1, which is the base arm order): sqlite3 cursor after SELECT: print `[[1], [2]]` then fetchall() 0 -> `<sqlite3.Cursor object>` then 2 (python: `<sqlite3.Cursor object at ...>`, 2); csv.reader: `[['a', 'b'], ['c', 'd']]` -> `<csv.CsvReader object>` (python `<_csv.reader object at ...>`; the name text is the #2104 Stdlib repr family), loop 2 both. Fixture-hit count: 0 existing .expected moved (one new fixture). Filtered counts, measured on a tree byte-identical to this commit (on base e7e0a0e): - Sharpy.Core.Tests (whole): 4021/0/0 - Sharpy.Stdlib.Tests (whole): 4273/0/0 - Compiler.Tests FileBasedIntegrationTests|DifferentialExecution: 3586/0/0 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…read(), enumerate() and active_count() report CPython's process-wide registry (refs #2261) #2261 part 2 (R-FD a, R-FF b): the process-wide state part 1 left out. Change. New Threading/ThreadRegistry.cs models CPython 3.12's threading.py: `_counter` (itertools.count(1), shared by Thread-N and Dummy-N; Interlocked), `_active` ∪ `_limbo` (a list under one lock, as CPython's _active_limbo_lock), and `_main_thread` (captured on the thread that first reaches the class, as CPython's `_MainThread()` runs on the importing thread; an explicit static constructor makes the first touch exact). `Thread.name` is a non-null `str`: the constructor's name, else `Thread-N` (an empty name also takes a number, as python's `if name:`), `MainThread` for the main thread. start() registers the thread before its .NET thread starts; the body's finally removes it, so it is listed while it runs and gone when join() returns. current_thread() returns the started Thread object itself (or the main thread, or a daemon `Dummy-N` that stays listed, as python's _DummyThread); main_thread() is the one main object; enumerate()/active_count() read the registry instead of the caller alone / the OS thread count. The ` (target)` suffix (`Thread-1 (f)`) is NOT here: it is a cell on #2105 (R-FD). Matrix (ThreadStateMatrixTests, extended; one program, 126 lines vs python3 3.12.13): operation {start, join, join(0.01), daemon get/set, is_alive, name get (new), name set} × state {initial, alive, finished} × host {Thread, Timer, Sub}; the current_thread() wrapper on main and worker and a worker on its own Thread; NEW: module function {current_thread().name, main_thread().name, main_thread() is current_thread(), ident equality, main_thread().is_alive(), main_thread().daemon, active_count(), len(enumerate()), enumerate() names} × caller {main, worker} + after the join; enumerate() membership and active_count() per host × state; current_thread() IS the host on each host's own thread. Default names are checked as numbers (Thread-16 … Thread-93). The assertion strips exactly " (<lambda>)" from the python lines (R-FD, #2105). Concurrency (P54 harness): two new roster rows ThreadRegistry._counter and ThreadRegistry._active (ExpectedWrittenGlobals 8 → 10), each with an exerciser at {write, read, cross-thread} × {1, 2, 8} threads, python-verified facts in the doc comments; new ThreadingRegistry collection shared with the threading spy tests (the cells assert exact numbers and counts). Mutations (build + ThreadStateMatrix|ThreadNameCells|ThreadActiveCells, 19 tests): - M1 current_thread() a fresh wrapper: 1 red (matrix) / 18 green - M2 main_thread() returns the caller: 1 red (matrix) - M3 the body never unregisters: 7 red (matrix + 6 active cells) - M4 start() never registers: 7 red (matrix + 6 active cells) - M5 `++_counter` instead of Interlocked: 2 red (names write/2 8/4001, write/8 724/16001) - M6 Register/Unregister without the lock: 2 red (active write/8, read/8) - M7 Thread-N counted from 0: 1 red (matrix) Restored (cp + touch, rebuild): 19/19 green. Direction record (`sharpyc run` of one probe; python3 3.12.13 in brackets): current_thread().name, main_thread().name: None, None -> MainThread, MainThread [MainThread, MainThread]; Thread(f).name / Sub().name / Timer().name: None x3 -> Thread-1, Thread-2, Thread-3 [Thread-1 (f), Thread-2, Thread-3]; active_count(), len(enumerate()) on main: 9, 1 -> 1, 1 [1, 1]; from a worker, main ident == current ident, len(enumerate()), active_count(), current is its Thread, names: "True 1 10 True None None" -> "False 2 2 True Thread-4 MainThread" [False 2 2 True Thread-4 (<lambda>) MainThread]; after the join active_count(): 9 -> 1 [1]. Expected values changed: none. Totality literals extended for the new rows: ExpectedWrittenGlobals 8 -> 10 and the every-operation module list (+2). Out of this part's scope (issue comments): an exception on a thread ending the process and Timer(1e10) (its member) are the target-raises cell, an operation outside this matrix. Filtered @ this commit's tree (parent e7e0a0e) (measured): Sharpy.Stdlib.Tests 4291/0/0; Compiler.Tests Stdlib|Roster|Census|Concurrency|ProcessGlobalState 373/0/0; format whitespace --verify-no-changes clean; allowlist issue-state clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…s it, and the .NET name next_double is gone (refs #2321) #2321 cell 1 (R-HL a). Contract: a stdlib module exposes the Python API, never the .NET name. Change (rung 1, Sharpy source): `def next_double()` in spy/random_module.spy is renamed `def random()` and next_double deleted (one way to do it); regenerated Random/Random.cs (RandomModuleModule.NextDouble -> Random), docs/stdlib/random.md, and the spy tests random_tests.spy / random_additional2_tests.spy (calls and the test names test_next_double_* -> test_random_*) with their generated C#; StdlibModuleGlobalConcurrencyTests' random exerciser calls Random(). No new global; StdlibModuleGlobalRosterTests unchanged. Hypothesis measured: the `Random as SysRandom` alias does not block the Python name — the emitter spells global::System.Random, so `def random()` compiles and the regen script, the spy-test regen and both staleness checks pass. Matrix: spelling {module attribute `random.random()`, from-import `from random import random as draw`} × {in [0, 1), seed-determinism} plus the deleted name refused — two new Stdlib.Tests fixtures (python3 3.12.13: True, True, True; AttributeError: module 'random' has no attribute 'next_double'): random_random_python_name_2321 (.expected) and random_next_double_refused_2321 (.error "Module 'random' has no member 'next_double'"). Mutation (rename back: every changed file reverted to the parent's bytes, the new fixtures kept; build Stdlib.Tests; FileBasedIntegrationTests 2321 filter): 2 red (random_next_double_refused_2321, random_random_python_name_2321) / 0 green; restored (cp + touch, rebuild): 2 green / 0 red. Direction record (`sharpyc run`; base = the e7e0a0e binary; random is unchanged at the parent 7c8dd7f4c): `import random; print(0 <= random.random() < 1)`: SPY0203 "Module 'random' has no member 'random'. Did you mean '_random'?" -> True [python True]; `print(0 <= random.next_double() < 1)`: True -> SPY0203 "Module 'random' has no member 'next_double'" [python AttributeError]. Expected values changed: none (the renamed spy tests keep their assertions). Not done (R-HL): cells 2-5 of #2321 and the other random.__all__ names. Filtered @ this commit's tree (measured): Sharpy.Stdlib.Tests 4293/0/0; Compiler.Tests Stdlib|Roster|Census|Concurrency|ProcessGlobalState 373/0/0; check_spy_staleness, check_spy_tests_staleness, stdlib docs regen, format whitespace --verify-no-changes and allowlist issue-state clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…orced at the one error-counting site, so no recovery follows the Nth error (refs #2311) Change (R-HD a). The parser read its cap at five parallel panic-mode catch sites (ParseModule, ParseStatements, ParseBlock, the enum body, the union body), each AFTER the statement parse that had already reported a cascade: a suite left at the cap reported its definition's `Expected Dedent` (SPY0104) as error N+1. Every parser error now goes through Parser.RecordError (ReportError and the two direct AddError sites, `raise ... from` and ParseSingleExpression's trailing token). When the error just recorded is the Nth it records the SPY0905 note and throws ParserErrorLimitException, which is not a ParserAbortException, so no recovery site catches it; the four public entry points (ParseModule, ParseStatements, ParseSingleStatement, ParseSingleExpression) stop there. The five catch-site copies are deleted. ParseStatements (the REPL) now records the SPY0905 note at the cap too; it used to stop silently. Matrix (MaxErrorsCapMatrixTests, Sharpy.Cli.Tests, 108 rows + count): phase {parser, semantic (control)} x N {1, 2, default: 25 parser / 100 semantic} x shape {statement, def body, class body, nested suite; parser also enum body, union body} x route {emit parse, emit diagnostics, run, build}; semantic x emit parse does not exist (no semantic phase). Each program has N+2 errors; assert exactly N errors and, on emit diagnostics, exactly one SPY0905. LSP route (MaxErrorsCapLspRouteTests, Sharpy.Lsp.Tests, 16 rows + count): SharpyWorkspace.GetAnalysisAsync and GetParseResultAsync, N = default only — the LSP has no cap option (ForLsp is reached with maxErrors 0), recorded rather than invented. Mutation (Rule 12), measured: - M1 drop the seam (RecordError never stops): Cli 72/109 red (every parser row), LSP 12/17 red (every parser row) - M2 the seam throws a recoverable ParserAbortException, so the catch sites recover past the cap: Cli 48/85 red, LSP 8/13 red (measured on the 4-shape matrix, before the enum/union shapes joined) - M3 the base parser (the five catch-site copies restored, no seam): Cli 52/109 red = def/class/nested x {1, 2, default} x 4 routes (36) + enum/union x {1, default} x 4 routes (16); LSP 10/17 red = the five non-statement parser shapes x 2 routes; statement and semantic rows green (controls). Restoring one catch-site copy WITH the seam in place is dead code (a ParserAbortException is only ever caught below the cap), so the base parser is the catch-site-copy mutation. - restored: Cli 109/0/0, LSP 17/0/0 Direction record (built sharpyc, base e7e0a0e vs this tree; errors counted, every route alike): - def/class/nested x N=1: base 2 (SPY0100 + SPY0104) -> 1 - def/class/nested x N=2: base 3 -> 2 - def/class/nested x default, 27 items: base 26 -> 25 - enum/union x N=1: base 2 -> 1; x default: base 26 -> 25; x N=2: 2 -> 2 (base already at the cap) - statement rows and every semantic row: unchanged (controls) Filtered runs @ e7e0a0e + this change (measured): - Compiler.Tests Parser|Conformance|Census|Roster|FileBasedIntegrationTests|Repl: 27231/0/2 - Lsp.Tests FrontEndParity|MaxErrorsCapLspRouteTests (4-shape): 14/0/0 - Cli.Tests MaxErrors|GlobalOption|CliOptionEffect (4-shape): 378/0/0 - Cli.Tests MaxErrorsCapMatrixTests (6-shape): 109/0/0; Lsp.Tests MaxErrorsCapLspRouteTests (6-shape): 17/0/0 No expected value changed. Out of scope, reported to the lead: an undefined type (SPY0202, type resolution) does not honour the semantic cap — three unions/defs with undefined types under --max-errors 1 report 3 errors on emit diagnostics and run, base and here alike; the semantic matrix therefore has no enum/union rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…tory unless --no-deps — build and project deploy like compile and run (refs #2313) Change (#2313 cell 0, ruling R-HF c). `build` and `project` wrote the assembly only, so `dotnet bin/Debug/net10.0/App.exe` died with FileNotFoundException; P46 documented that as by design (10d0b3d). They are now two more callers of the one deployment seam, RuntimeDependencyHelper.CopyRuntimeDependencies: - build, in-process: seeded with CompileResult.UsedAssemblyPaths plus the `-r` list resolved as CompileToBinary hands it to the compiler (compile's seed); `build --server`: the server's reply carries the used assemblies (CompileServerClient.TryRun now returns them), and the client deploys, as `run --server` does. One OutputDirectory rule for both routes and CompileToBinary. - project: seeded with every default reference plus the .spyproj's <Reference> items and the merged `-r`, captured before the defaults join the list — `compile app.spyproj`'s seed. Both gain `--no-deps`, spelled and described exactly as `compile`'s. Registry: `--no-deps` rows (Honoured) in the build and project tables, evidence ReferenceDeploymentMatrixTests.NoDeps_WritesTheAssemblyOnly. `--help`: build/project descriptions say they deploy (`--no-deps` writes the assembly only); the `-r` "not deployed" reason is gone. README (Build/Project options, Options-by-Command deploy column, Output Structure), module_system.md (Deployment paragraph) and gap-discovery-contracts.md reworded; the Output Structure listing is the measured `project` output below. GlobalOptionProbes invoke build and project with `--no-deps`, as they already did compile, so the globals probes stay deployment-free. Matrix (ReferenceDeploymentMatrixTests): reference source {-r absolute, -r bare name + -m, <Reference Include>, <HintPath> absolute, <HintPath> relative} x route {compile .spy, compile .spyproj, build, build --server, project, run, run --server} where the source applies (<Reference> sources: .spyproj routes only) = 20 honoured cells, each RUN with `dotnet <assembly>` from its output directory and asserting the library and Sharpy.Core.dll beside it; x --no-deps on every route that takes it = 16 cells asserting the assembly (positive control) and neither file. The four former assembly-only controls are subsumed (build/project became honoured cells). ReadmeOptionTableTests: DeployingCommands = {build, compile, project, run}; the deploy cell of each --no-deps command must name the opt-out; build/project help carry the deploy sentence; no deploying command's help carries the old assembly-only sentence or "not deployed beside the output". Mutation (Rule 12), measured on ReferenceDeploymentMatrixTests + ReadmeOptionTableTests: - M-a drop build's in-process caller + build help back to the assembly-only sentence: 4/50 red = [r-absolute|r-bare-name+m x build] + ABuildCommand_SaysItDeploysInItsHelp(build) + ADeployingCommand_HasNoAssemblyOnlySentence(build) - M-b drop project's caller + README project row back to "no — writes the assembly only": 6/50 red = the 5 [* x project] cells + TheDeploysColumn_MatchesTheDeployingCommands - M-c drop build's server-route caller + project ignores --no-deps: 7/50 red = [r-* x build --server] (2) + NoDeps [* x project] (5) - restored: 50/0/0 Direction record (built sharpyc, base e7e0a0e vs this tree; a .spyproj with a <HintPath> reference, and build -r): - base `project app/App.spyproj`: bin/Debug/net10.0 = App.deps.json App.exe App.pdb App.runtimeconfig.json; `dotnet App.exe` -> FileNotFoundException 'Tablegreeter, Version=0.0.0.0', exit 134. Now: + Sharpy.Core.dll, Sharpy.Stdlib.dll and its dependencies, Tablegreeter.dll; `dotnet App.exe` prints "hello from lib", exit 0. - base `build main.spy -r Tablegreeter.dll -o out/main.dll`: out = main .deps.json/.dll/.pdb/.runtimeconfig.json; `dotnet main.dll` -> FileNotFoundException, exit 134. Now: + Sharpy.Core.dll, Tablegreeter.dll, libe_sqlite3.dylib; runs, exit 0. - `--no-deps` on build/project: base "Unrecognized command or argument '--no-deps'", exit 1. Now: exit 0, the base layout (the four files). Filtered counts @ 344402c44 + this change (measured): - Sharpy.Cli.Tests (whole project): 1033/0/1 - build_tools/check_spec_blocks.sh (built sharpyc): clean, 0 failing - dotnet format whitespace sharpy.sln --verify-no-changes: clean Expected values changed (they pinned the behaviour this cures): ReadmeOptionTableTests.DeployingCommands/AssemblyOnlyCommands, the deploy-cell rule, AWrongDeploysCell_IsReported's spelling, and the help tests (AnAssemblyOnlyCommand_SaysSoInItsHelp -> ABuildCommand_SaysItDeploysInItsHelp); ReferenceDeploymentMatrixTests' Control_TheOptOuts_WriteTheAssemblyOnly build/project rows (now honoured cells; the compile --no-deps rows live on in NoDeps_WritesTheAssemblyOnly). Not done: the compile server's `project` request (ProjectCommand.CompileProjectForServer) has no CLI client and does not deploy; no producing command reaches it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…210 at one seam — 1e400, 1e39f and 1e29m never reach Roslyn or the emitter (refs #2340) R-HU (a): the float half of the literal-range seam. FloatLiteralClassifier (Shared/, twin of IntegerLiteralClassifier) types a float literal by suffix and asks the ONE range question, Fits(text, double|float|decimal); the literal-typing arm of CheckExpression refuses a miss with SPY0210 ("Float literal '1e400' is too large for 'double'" / 'float' / 'decimal'), and the float32/decimal slot-narrowing predicates (ImplicitConversions) ask the same Fits, so the suffixed and the slot-narrowed spelling of one value cannot disagree. float32 is now parsed as a float directly (Roslyn's own verdict on the emitted `...f` text), not routed through double. SPY0210's constant is renamed NumericLiteralOutOfRange; its DiagnosticExplanations entry names the float types. float_literals.md gains "A Literal Must Fit Its Own Type" beside the float32 limit, and the decimal slot limit (`d: decimal = 1e29` SPY0220). The theory's negative x unsigned cells were not range cells: CheckUnaryOp's negated-literal arm typed `-1U` 'uint32' (C#: long) and handed `-1UL` to C# (CS0023), both SPY0908. Only an unsuffixed/L literal now negates as a literal (IntegerLiteralClassifier.NegatesAsLiteral); `-1U` / `-1UL` take the unary operator path, int64 and SPY0223, exactly as `-v` on a uint32/uint64 variable. Matrix (NumericLiteralRangeMatrixTests, 84 rows): type {int32->int64 promotion, int64, uint32, uint64, float64, float32, decimal} x spelling {unsuffixed, suffixed, slot-narrowed} x magnitude {max, max + one unit/ulp, far, negative}; axis changes POSITION {pattern, parameter default, const, field, lambda body, f-string} and CASE {1E400, F, M}; plus a Roslyn differential (13 texts: the seam admits exactly what CS0594 admits). Mutation (Rule 12): - float64 arm of Fits -> `return true`: 14 red (8 float64 cells, the 2 PastFloat64 slot cells, 4 positions, 2 Roslyn rows; 70 green) -> restored 84/0/0. - NegatesAsLiteral -> `true` + float32 narrowing double-routed again: 3 red (UInt64_Suffixed_Negative, UInt32_Suffixed_Negative_IntoUInt32Slot, Float32_SlotNarrowed_BelowMidpoint; 81 green) -> restored 84/0/0. Direction (run, base e7e0a0e -> this commit): - SPY0599 (CS0594) -> SPY0210: print(1e400), print(-1e400), print(1e400d), print(1.7976931348623159e308), x: float = 1e400, print(1e39f), print(-1e39f), print(3.4028236e38f), case 1e400:, def f(x: float = 1e400), const C: float = 1e400. - SPY0909 ICE -> SPY0210: print(1e29m), print(-1e29m), print(79228162514264337593543950336m), class field `v: decimal = 1e29m`. - SPY0908 -> SPY0223: print(-1UL). SPY0908 -> SPY0220: x: uint32 = -1U. - SPY0220 -> SPY0210 + SPY0220 (still refused, cause named): x: float32 = 1e400, d: decimal = 1e400. - SPY0220 -> runs (a false refusal cured; the suffixed spelling already ran): x: float32 = 340282356779733661637539395458142568447.0 prints 3.4028235e+38 — below the float32 rounding midpoint, double-rounded to inf by the old predicate. - Unchanged: every in-range cell, print(-1U) -> -1, print(1e-400) -> 0.0, print(1e-30m) -> 0.0000000000000000000000000000, all integer refusals. No program that ran changes its output. No fixture or snapshot moves. Filtered @ this tree (measured): Compiler.Tests NumericLiteralRangeMatrix | TypeChecker | Semantic | Conformance | Census | Roster | EmitterCarrier | ProcessWide | FileBasedIntegrationTests 32311/0/1; Lsp.Tests FrontEndParity 1/0/0; Stdlib.Tests 4273/0/0; build 0 warnings; check_spec_blocks.sh clean (1207 blocks, failing 0); the new spec block run: prints the three maxima. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…ported CLR name is an open generic is a CodeGenInfo fact decided against the registry's visibility (refs #2320) R-HM (a). CodeGenInfo gains IsOpenGenericDefinition: for a from-imported type symbol, the name denotes a generic type definition — its own type parameters, or a generic member of the CLR arity group discovery built for it (ModuleRegistry.GetNamespaceTypes, against the registry's own visibility: framework + references). CodeGenInfoComputer.ProcessFromImport records it; it freezes with the entry at MaterializeCodeGenInfo. The emitter's alias loop reads it and nothing else; IsClrTypeGenericInNamespace, its AssemblyVisibility.FrameworkOnly read and the TODO(#2320) are deleted. SemanticBinding.MergeFrom carries the fact onto an existing entry, as it carries the layout (#2102): an `as`-aliased import binds a clone whose import-resolution entry is merged first, and without the arm `from system import Action as A` emitted `using A = global::System.Action;` (measured on the first cut of this change, which the old reflection had skipped). SymbolFactMirrorConformanceTests classifies the new member same-file-only. Premise correction: neither EmitterCarrierOnlyConformanceTests nor ProcessWideResolutionScanTests had a census entry for this site — the first's deny universe is the Semantic namespaces (AssemblyVisibility is in Discovery) and the second matches process-wide sites (AppDomain, Type.GetType, Assembly.Load*), not AssemblyVisibility reads. Nothing to drain; the guard is new instead: EmitterCarrierOnlyConformanceTests. CodeGenSources_NameNoClrVisibilitySeam denies CodeGen every Discovery type that IS AssemblyVisibility or carries it on its non-private surface (derived by reflection, so a new resolver taking the seam is denied the day it is added), with SyntheticVisibilitySeamRead_IsFlagged as positive/negative control (the deleted read in three spellings flagged; ClrTypeBridge's catalog lookup not). Matrix (OpenGenericImportAliasTests, both cells run and print): visibility {framework, referenced System.Widgets built in-test} x spelling {bare, as} x name {arity group with a generic member, non-generic (positive control: alias emitted)}. Mutation (Rule 12): - emitter re-reads AssemblyVisibility.FrameworkOnly: 1 red (CodeGenSources_NameNoClrVisibilitySeam; 18 green) -> restored 19/0/0. - MergeFrom arm disabled: 1 red (Framework_ArityGroupIsNotAliased_BareOrAsAlias; 18 green) -> restored. - DenotesGenericDefinition ignores the arity group: 2 red (both OpenGenericImportAliasTests; 17 green) -> restored 19/0/0. Direction (run, base e7e0a0e -> this commit): - framework `from system import Action, Nullable, Math, Guid, Tuple` + `from system.collections.generic import IList, Dictionary, KeyValuePair` + `from system import Action as A`: prints 2/4/9/2 before and after; generated C# byte-identical. - referenced System.Widgets.dll (Box, Box<T>, Pair<A,B>, Gadget) via -r: prints a/box/7 before and after; the only C# change is `using Box = global::System.Widgets.Box;` no longer emitted (Box<T> is visible through the reference, which the framework-only read missed). No snapshot moved (FileBasedIntegrationTests green). Filtered @ this tree (measured): Compiler.Tests OpenGenericImportAlias | Compiler.Tests.CodeGen | NumericLiteralRangeMatrix | TypeChecker | Semantic | Conformance | Census | Roster | EmitterCarrier | ProcessWide | FileBasedIntegrationTests 33751/0/1; Lsp.Tests FrontEndParity 1/0/0; Stdlib.Tests 4273/0/0; build 0 warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…on every ReadNumber path (refs #2339) Contract (integer_literals.md / float_literals.md): every suffix the spec lists is accepted on every spelling of its literal kind and refused (SPY0023) on every other kind. Class: the parallel-path meta-class — the hex/bin/oct path was keyed by kind since #2208 (IntegerSuffixes), the decimal path validated against one kind-blind roster, and the leading-dot path returned before any suffix arm. Cure (R-HX a): ONE table, Lexer.NumericSuffixes (suffix -> NumericSuffixKind {Integer, Float}), and ONE reader, ReadNumericSuffix(sb, NumberSpelling), called by all five paths (decimal, leading-dot, hex, binary, octal) after their digits. SpellingAccepts states the C# rule: decimal-integer digits take both kinds (42L; 42f is a float), a float spelling (fraction or exponent, any spelling) takes only f/d/m, a prefixed literal only u/l/ul. The prefixed-literal refusal text is unchanged (the spec quotes it); a float spelling's refusal says "(a float literal accepts only the float suffixes f, d and m)". The table is an ImmutableDictionary (StaticStateConformanceTests refused a static Dictionary). float_literals.md gains the sentence and an error example for 3.14L / 1e5u. Fixture-hit count FIRST (grep over every .spy/.md under src docs build_tools examples): integer suffix on a float spelling 0, leading-dot literal with a suffix 0 in code position (3 hits, all inside format-spec strings such as ",.1f"). Positive control of the grep: 58 float-suffix hits. Matrix (NumericLiteralTwinTests.SharpyOnlyRows_LexAsTheSpecSays, now generated from SuffixSpellings): kind {decimal int, decimal float, hex, binary/octal} x spelling {42, 1_000, 0.5, 3.14, .5, .1_5, 1e5, 1E+5, .5e3, 1_0.2_5, 1_0e1_0, 0xFF, 0xF_F, 0b101, 0b1_01, 0o17, 0o1_7} x suffix {l L u U ul UL uL Ul, f F d D m M, j J lu x, none} (both cases carried by the rosters) + the trailing-dot 5.<every suffix> rows (a letter after the dot is a member name) = 345 rows (was 83). The old comment admitting the integer-suffix-on-float cell as unpinned is gone. Direction record (sharpyc run, base binary @ e7e0a0e -> this commit): print(3.14L, 1e5u) 3.14 100000.0 -> SPY0023 Invalid numeric suffix: L (a float literal ...) a: int64 = 3.14L SPY0220 float64 -> SPY0023 print(3.14u) runs -> SPY0023 print(1.5ul) SPY0909 ICE (FormatException '1.5u') -> SPY0023 y = .5f; print(y) SPY0103 got Identifier -> 0.5 print(.5f, .5m, .5d, .5e3f, .1_5F) SPY0104 -> 0.5 0.5 0.5 500.0 0.15 a: int = .5f / .5m (SPY0103) -> SPY0220 'float32' / 'decimal' (same as 0.5f) print(.5j) SPY0104 got Identifier -> SPY0023 print(.5x) SPY0104 -> SPY0023 (as 0.5x) print(1e5f, 1E5m, 1_0e1_0d) 100000.0 100000 100000000000.0 -> unchanged print(1j, 1.5j) SPY0023 -> unchanged Mutation (Rule 12; NumericLiteralTwin filter, 348 tests): M1 leading-dot path skips ReadNumericSuffix -> 294/54/0, the 54 red rows exactly the 3 leading-dot spellings x 18 suffixed rows M2 SpellingAccepts(DecimalFloat) => true -> 276/72/0, the 72 red rows exactly the 9 float spellings x 8 integer suffixes restored (cp + touch, rebuilt) -> all 348 green (inside the 23799/0/5 run below) (measured on the Dictionary spelling of the table; the ImmutableDictionary swap changes no lookup.) Filtered runs @ this commit's tree (measured): Compiler.Tests Lexer|IntegerSuffix|Conformance|Census|Roster| FileBasedIntegrationTests|StaticState 23799/0/5 Lsp.Tests FrontEndParity 1/0/0 Lsp.Tests Category!=Benchmark 21944/0/0 dotnet format whitespace --verify-no-changes clean check_spec_blocks.sh clean (failing 0) No expected value changed; no snapshot changed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…er letters are recorded deviations; the numeric twin pins 01 by the ledger, not the allowlist (refs #2300) Two DEVIATION-by-default surfaces, RECORDED by ruling (R-HT, R-HW); #2300 stays open. Refusing leading-zero integers was rejected and is not done. 1. leading-zero-decimal-integer (docs/deviations.yaml; category syntax, audience python, severity none). integer_literals.md gains "Leading zeros are digits (C#'s rule)" with an output-asserted spec block. Measured (python3 3.12.13; sharpyc run @ e7e0a0e, unchanged here): print(01, 0_1, 007) Sharpy: 1 1 7 print(01) / print(007) / print(0_0_7) CPython: SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers print(00, 0_0) both: 0 0 (all-zero: CPython accepts) print(01.5, 01e3) CPython: 1.5 1000.0 (a FLOAT's leading zero is fine) print(0__1) CPython: invalid decimal literal (Sharpy refuses too) The twin: the two allowlist rows 01 / 0_1 (numeric-literal-twin-allowlist.txt) are deleted; NumericLiteralTwinTests.SharpyOnly gains the row "leading-zero decimal integer" (predicate ^(?=[0-9_]*[1-9])0(?:_?[0-9])+$, the spec sentence + ledger id) and each SharpyOnly row now carries the Sharpy verdict it pins (SharpyAccepts): a trailing-dot float is refused by Sharpy and accepted by CPython, a leading-zero integer the reverse; the OVER-BROAD check runs in the matching direction. The predicate excludes floats (01.5, 01e3), all-zero integers (00, 0_0) and the malformed 0__1 / 01_, which stay compared — anchored by literal ExclusionOf asserts. SharpyOnlyRows_LexAsTheSpecSays gains 01, 0_1, 007, 0_0_7, 01L. The allowlist positive-control row that used the key "01" now uses "10" (01 is no longer a compared spelling, which would add a 5th violation to a control that counts 4) — the control's input, not an expected value. 2. identifier-letters-bmp-only (category syntax, audience python, severity error, code SPY0015). identifiers.md: "letters of the Basic Multilingual Plane — C#'s identifier alphabet (Axiom 1)". Measured: 𝒳 = 1 (U+1D4B3) CPython: binds, print(𝒳) -> 1 Sharpy: SPY0015 Unexpected character: '<U+FFFD>' — the message holds the lone high surrogate U+D835 (bytes EF BF BD on the terminal), once per line using 𝒳 été = 1 Sharpy: runs, prints 1 (control) The full-code-point message (optional courtesy) is not done. build_tools/cpython_oracle/ledger.yaml regenerated (ledger --write; --check clean: 104 deviations). Mutation (Rule 12; NumericLiteralTwin filter): M3 the lexer refuses a leading-zero decimal integer -> 347/6/0: the 5 new SharpyOnlyRows (01 0_1 007 0_0_7 01L) + SharedGrammar_AgreesWithCPython (SHARPY-ONLY "01", "0_1": the spec accepts it, Sharpy gives refused) M4 the SharpyOnly row's SharpyAccepts flipped to false -> 352/1/0: SharedGrammar red with SHARPY-ONLY + OVER-BROAD on "01" and "0_1" restored (cp + touch, rebuilt) -> green (inside the run below) Spec block positive control: "# 7 1 7" -> "# 7 1 8" -> RED integer_literals.md:20 output mismatch (expected 7 1 8, actual 7 1 7); restored -> clean. Filtered runs @ this commit's tree (measured): Compiler.Tests NumericLiteralTwin 353/0/0 Compiler.Tests Lexer|Conformance|Census|Roster 20197/0/5 check_spec_blocks.sh clean (output-asserted 2, failing 0) pytest test_cpython_oracle_ledger + test_allowlist_issue_state + test_cpython_oracle 82 passed python -m build_tools.cpython_oracle ledger --check up to date check_allowlist_issue_state.sh exit 0 Expected values changed: none (the control row key above is an input). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…ed, never Passed — one RequireOrSkip gate + a SKIP: census (refs #2342) Contract (verification contract §2): a test that cannot reach its oracle reports SKIPPED, never PASSED. Ruling R-HZ (a). - Xunit.SkippableFact 1.5.85 (published 2026-08-24, nuget.org; deps Validation 2.6.68 + xunit.extensibility.execution >= 2.4.0) added to Sharpy.TestInfrastructure (SkipException) and Sharpy.Compiler.Tests (the attributes) — the repo pins versions per csproj, no central file. - ONE helper, PythonInterpreter.RequireOrSkip(minMinor: 12) (+ an overload with the candidates and the required flag explicit): located -> returned; absent -> SkipException; absent + SHARPY_REQUIRE_PYTHON_ORACLE=1 -> PythonOracleMissingException naming the env var and the candidates. - The eight write-SKIP:-and-return sites (re-grepped: exactly these eight in every src/*Tests* and TestInfrastructure .cs) now open with RequireOrSkip under [SkippableFact]/[SkippableTheory]: PythonInterpreterTests x2, NumericLiteralTwinTests.SharedGrammar, FormatSpecDifferentialTests x2, DifferentialExecutionTests x1, CPythonDifferentialParseTests x2 (one a Theory, 14 rows). A site whose python is present but whose in-repo oracle script is missing now FAILS (FileNotFoundException) — a broken checkout is not a skip. - Census guard PythonOracleSkipCensusTests (Roslyn syntax scan of every .cs under src/*.Tests and src/Sharpy.TestInfrastructure, minus bin/obj/ TestFixtures): no string starting "SKIP:"; a [Fact]/[Theory] calling RequireOrSkip (outside a lambda) must be [SkippableFact]/[SkippableTheory] (under a plain [Fact] the SkipException FAILS); >= 8 skippable callers (literal floor) and > 500 files scanned (anti-vacuity). Positive control Census_FiresOnEachForbiddenShape: a built source with a SKIP: write, an interpolated $"SKIP: {why}", a [Theory] calling RequireOrSkip -> lines 9/15/18 flagged; the [SkippableFact]/[SkippableTheory] and the Assert.Throws(() => RequireOrSkip(...)) shapes pass. - PythonInterpreterTests gains the two verdict tests RequireOrSkip_RequiredAndMissing_Fails_NamingTheOracle and RequireOrSkip_NotRequiredAndMissing_Skips (unresolvable candidate list, flag explicit — never an in-process env override). - Sibling seam found by the filtered run: CiFilterCoverageConformanceTests enumerated tests by attribute NAME ("FactAttribute"/"TheoryAttribute"), so a [SkippableFact] dropped out of its universe — FormatSpecDifferential- Tests (both its tests now skippable) went red as "FQN filter substring matches no test", and the CI-selection check would have silently stopped covering every skippable test. It now matches by derivation (typeof(FactAttribute).IsAssignableFrom). Direction: red (EveryFilterSubstring_MatchesAtLeastOneTest, 8x "FormatSpecDifferential") before the fix, green after (run below). Mutation / direction record — the eight sites (24 test cases at base, 26 after: + the 2 verdict tests) run in a CHILD `dotnet test` whose PATH is scrubbed to a dotnet shim + /usr/bin:/bin:/usr/sbin:/sbin (python3 there is 3.9.6 < 3.12); the env var is set only in the child (measured): base @ a60d16bc5 this commit python present 24/0/0 26/0/0 (positive control: all 21 site cases pass both) PATH scrubbed 24/0/0 (vacuous) 5/0/21 (every site Skipped, reason names the env var) scrubbed + env=1 21/3/0 (only the 3 5/21/0 (every site fails: interpreter sites) PythonOracleMissingException) restored (python present again) = the 26/0/0 row. Census mutation: RunBatch's [SkippableFact] -> [Fact] and a `Output.WriteLine("SKIP: mutant")` at both FormatSpec sites -> 1/1/0, the census red listing exactly 3 violations (PythonInterpreterTests.cs:82 under [Fact], FormatSpecDifferentialTests.cs:118 and :267 SKIP: strings), the positive control green; restored (cp + touch, rebuilt) -> green. Filtered runs @ this commit's tree (measured): Compiler.Tests Lexer|Conformance|Census|Roster|Differential before the CiFilterCoverage fix 20384/1/5 (the red above) Compiler.Tests Conformance|PythonInterpreterTests after it 17496/0/0 dotnet format whitespace --verify-no-changes clean (measured before the 4-line CiFilterCoverage edit) Expected values changed: none. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…ture run-alone roster — it swaps Console.Out like Print_Tests (refs #2057) The #2057 chore (2f2c38f) added Sharpy.Core.Tests.SharpyInstanceFormattingTests to [Collection("ConsoleCapture")] (its print site captures stdout through Console.SetOut) without a SerialCollectionRoster row. ProcessGlobalStateConformanceTests caught it in the band's gate @ fed8a7c: EveryMemberOfARunAloneCollection_IsRostered ("+ Sharpy.Core.Tests.SharpyInstanceFormattingTests in \"ConsoleCapture\"") and PositiveControl_TheRosterIsFoundInFull (members 54 expected, 55 found). The roster row gains the member; the hand-counted ExpectedSerialMembers goes 54 -> 55. No guard is new, so no mutation is owed: the two reds above are the existing guard firing on this exact omission. ProcessGlobalStateConformanceTests|InProcessIsolationSweepTests: 38/0/0 (measured, this tree). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
… under #2343 — a reused Compiler reports the assemblies an earlier compile used (refs #2343) The band's gate @ fed8a7c failed InProcessIsolationSweepTests.Sweep_EightThreads_EightRepetitions: 98 cells, all `compiler-per-thread 8 {analyze,compile} drift`, each reproduced 8/8, each the same shape: solo `used=[]`, observed `used=[.../Sharpy.Core.dll]` for a program that imports nothing. Attribution (measured, verification-contract §5–§6): - The sweep alone on this tree: 98 red (not load). - Each chore branch alone (same base e7e0a0e): semantic, lexer, stdlib and parser/cli 0 red; core (a7456f5b4) 98 red. - Control: this tree with the core chore's three Core sources reverted to 96d7a27 and its new fixture kept: still 98 red. So the cause is the fixture's PRESENCE, not the Core change. The corpus sample is every 16th single-file fixture by sorted name; inserting builtins/iterable_instance_str_2057 moved 194 of the 213 sampled members. The newly sampled builtins_qualified_value_position_1463 imports `builtins`, a Core module. - Mechanism (read): Compiler builds one ModuleRegistry and reuses it; RecordModuleUsage fills _usedAssemblyPaths and nothing clears it. Filed as #2343 (class-tracker, lane:silent-wrong, reach:probe) with the cell matrix and acceptance. One row per red cell, each citing #2343 with `reproduced 8/8 @ fed8a7c`. The rows drain when #2343's cure lands (the ratchet fails a stale row). No guard is new; the sweep and its stale-row check are the existing instrument. ProcessGlobalStateConformanceTests|InProcessIsolationSweepTests: 38/0/0 (measured, this tree). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
…diagnostics pins count the #2057 fixture — one new corpus stem in two existing classes (refs #2294, refs #2296) The band's gate @ fed8a7c failed CodeActionShapeSweepTests.Identity_EveryCodeActionKeepsTheSourceShape: regular `wrap-try identity diagnostics #2296` pinned 374, actual 375. The one added stem is builtins/iterable_instance_str_2057 (the #2057 chore's new run fixture): wrapping its statements in try changes its diagnostics, the cell the #2296 pin already holds for 374 other stems. Re-pinned from a FULL run (SHARPY_CODEACTION_SWEEP_FULL=1, DOP 8; the allowlist header's regeneration rule) on this tree. Exactly two keys moved in each of the four twins, each by +1, and in every one the added stem is that fixture (checked in <twin>-full-mismatched-stems.txt): - extract-method {identity,crlf,cr,comment} diagnostics #2294: full 2870 -> 2871 (regular unchanged) - wrap-try {identity,crlf,cr,comment} diagnostics #2296: full 2311 -> 2312; identity regular 374 -> 375 Every other row of the four actual-pins files equals the committed row. No guard is new; the sweep's pin check is the existing instrument, and its red named this stem. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW
This was referenced Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The call-27 chores band: ten chores, one commit each, plus three lead commits that fixed the first gate's reds.
ReadNumberpath (3.14Lrefused,.5fruns)1e400,1e39f,1e29m)SkippableFact+RequireOrSkip)leading-zero-decimal-integer,identifier-letters-bmp-onlyThread-N/MainThread,main_thread(),enumerate(),active_count()--max-errors Nreports at most N parser errors (one cap seam)buildandprojectdeploy dependencies;--no-depsCodeGenInfo.IsOpenGenericDefinitionrandom.random();next_doubleremovedLead commits: ff76b8d (run-alone roster row), a7cd29e (98 isolation-sweep rows citing #2343, exposed by the new fixture's shift of the stride-16 sample; attributed by a control with the Core change reverted), 4d22212 (code-action pins +1 for the new fixture, re-pinned from a full sweep).
Gate: whole solution 81669/0/7 @ 4d22212 (measured); staleness (spy, spy-tests, stdlib docs, oracle ledger), spec blocks, whitespace, allowlist issue-state and build_tools pytest clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_0126pNZqmMsSbn4WFFPhpnxW